# Coritan Docs > Guides and API reference for Coritan: Container Apps, Cloud Compute, Apps, Object Storage, Mail Hosting, SMTP Relay, websites and DNS, Edge Proxy, Floating IPs, DDoS Shield, billing and organizations. Every guide follows. The API reference is in https://www.coritan.com/docs/api/llms-full.txt. # Get started with Coritan > Create an account, add credit, order your first service and manage it from the dashboard or through the API. Source: https://www.coritan.com/docs/get-started/ In the dashboard: - /: https://www.coritan.com/ Coritan runs hosting for you: game and app servers on Container Apps, virtual machines on Cloud Compute, Object Storage, Mail Hosting and SMTP Relay, websites and DNS, Edge Proxy, Floating IPs and DDoS Shield. You order and run all of it from one account, in the dashboard at [coritan.com](https://www.coritan.com) or through the API. This section takes you from a new account to a running service. The home page at [coritan.com](https://www.coritan.com) has **Sign up** and **Sign in** in its header. Once you are signed in, the same place shows **Dashboard**. ## First steps 1. [Create an account](/docs/get-started/create-an-account/) with your email address, or with a new `@coritan.gg` mailbox. 2. Find your way around [the dashboard](/docs/get-started/dashboard/): the sidebar, search and your credit balance. 3. [Order a service](/docs/get-started/order-a-service/): choose a product, a plan, a location and a billing cycle. 4. [Manage your services](/docs/get-started/services/) from one list, and read each one's status and history. 5. [Tag your services](/docs/get-started/tags/) so you can filter a long list. ## Automate it - [Make your first API request](/docs/get-started/first-steps-with-the-api/) with an access token. - The [Coritan API](/docs/api/) section covers authentication, errors, rate limits and paging. ## Products Each product has its own section: - [Container Apps](/docs/managed-containers/) for game and app servers - [Cloud Compute](/docs/cloud-compute/) for virtual machines, called instances - [Object Storage](/docs/object-storage/) and [Snapshots](/docs/snapshots/) - [Mail Hosting and SMTP Relay](/docs/mail/) - [Websites and DNS](/docs/websites/), [Edge Proxy](/docs/proxies/), [Floating IPs](/docs/floating-ips/) and [DDoS Shield](/docs/ddos-shield/) - [Organizations](/docs/organizations/), to sell Coritan products under your own brand ## Billing You pay for services from account credit or with a saved payment method, on the billing cycle you choose when you order. [Billing](/docs/billing/) explains credit, invoices, payment methods and hourly billing. ## Limits - Hourly billing opens only after you have added a minimum amount of credit in total. [How hourly billing works](/docs/billing/hourly-billing/) explains the rule. - Bare metal servers, colocation and IP transit are not on the order pages. You arrange them with the sales team: see [Bare metal, colocation and IP transit](/docs/platform/sales-led-products/). - For help at any point, [contact support](/docs/support/). # Create an account > Sign up with your own email address or with a new Coritan mailbox, then sign in to the dashboard. Source: https://www.coritan.com/docs/get-started/create-an-account/ In the dashboard: - /sign-up: https://www.coritan.com/sign-up Create a Coritan account to order and manage services. You sign in with an email address you already have, or with a new `@coritan.gg` mailbox that Coritan creates for you at the same time. You do not need to confirm the address before you use the account. ## Before you begin - Choose a password of 8–128 characters that has at least one lowercase letter, one uppercase letter, one digit and one symbol. - If you sign up with your own address, use one you can read. Password reset links go to it. ## Sign up with your email address 1. Go to [coritan.com/sign-up](https://www.coritan.com/sign-up), or select **Sign up** at the top of the home page. 2. Enter your **First name** and **Last name**. Both are optional, and you can add them later in your profile. 3. If the form shows **How to sign in**, keep **Use your email** selected. 4. Enter your address in **Work email**. 5. Enter a **Password**. The hint under the field lists what it still needs, and shows **Meets every rule** when it passes. 6. Enter the same password in **Confirm password**. 7. If a verification check appears, complete it. 8. Select **Create account**. ## Sign up with a new mailbox When Coritan offers mailboxes at sign-up, **How to sign in** has a second option that creates a `@coritan.gg` inbox. The new address becomes the email you sign in with, and the inbox opens in Webmail. 1. On the sign-up form, under **How to sign in**, choose the option to create a `@coritan.gg` inbox. 2. Type a name in **Your new address**. Use lowercase letters, digits, dots, hyphens and underscores, up to 40 characters, starting and ending with a letter or a digit. The form checks the name as you type and says whether it is available. 3. Optionally, enter a **Recovery email**. A password reset link goes to this address as well as to the new inbox, so add one if you can. 4. Enter a **Password** and type it again in **Confirm password**. The mailbox uses the same password. 5. If a verification check appears, complete it. 6. Select **Create account and mailbox**. ## Result You are signed in. With your own address, the [dashboard](/docs/get-started/dashboard/) opens and offers you a free `coritan.gg` name, which you can [claim now or later](/docs/proxies/coritan-gg-names/). With a new mailbox, [webmail](/docs/mail/webmail/) opens on your inbox and says `Your inbox is ready.` Coritan sends a welcome email to the account's address. Next, [add credit](/docs/billing/add-credit/) or [order a service](/docs/get-started/order-a-service/). ## Troubleshooting `Email already registered.` : An account already uses that address. Select **Sign in**, or **reset your password** if you do not remember it. The password hint says `Still needs` something : The password is missing a character type or is shorter than 8 characters. Add what the hint lists. `Complete the verification check to continue.` : The verification check has not finished. Complete it, then select the button again. `The verification check did not pass. Complete it and try again.` : The check expired or was refused. It resets after every attempt, so complete it again. `That address is already taken.` or `That name is reserved.` : Someone has that mailbox name, or Coritan keeps it back. Choose another name. The form switches back to **Use your email** with a notice : Coritan could not create mailboxes at that moment. Sign up with an address you already have, or try the mailbox again in a few minutes. `Too many authentication attempts. Please try again later.` : Your network sent too many sign-up attempts that failed. Wait a minute and try again. [Rate limits](/docs/api/rate-limits/) lists every limit. ## Related - [Sign in to your account](/docs/account/sign-in/) - [Turn on two-factor authentication](/docs/account/two-factor-authentication/) - [Update your profile](/docs/account/profile/) ## With the API Check first whether sign-up needs a verification token. The endpoint needs no authentication: ```bash curl https://api.coritan.com/api/v1/auth/turnstile ``` ```json {"enabled": false, "site_key": ""} ``` When `enabled` is `true`, every sign-up request needs a `turnstile_token` that only the widget in a browser can produce, so create the account on the website instead. Create the account with [`POST /auth/register`](/docs/api/reference/client/authentication/#op-post-api-v1-auth-register). Send exactly one of `email` and `mailbox_local_part`. The other fields are `password`, and optionally `first_name`, `last_name`, `company`, `recovery_email` (with a mailbox only) and `turnstile_token`. ```bash curl -X POST https://api.coritan.com/api/v1/auth/register \ -H "Content-Type: application/json" \ -d '{"email": "alex@example.com", "password": "Correct-horse-9", "first_name": "Alex"}' ``` The answer is a token pair, so the new account is signed in at once. `expires_in` is the access token's lifetime in seconds. ```json { "access_token": "eyJhbGciOiJIUzI1NiIs...", "refresh_token": "eyJhbGciOiJIUzI1NiIs...", "token_type": "bearer", "expires_in": 1800 } ``` A password that breaks a rule answers `422` with the rule, such as `Password must contain at least one uppercase letter`. An address with an account answers `409` `Email already registered`. To offer a mailbox, ask which domain the storefront uses with [`GET /auth/mailbox-domain`](/docs/api/reference/client/authentication/#op-get-api-v1-auth-mailbox-domain), then check a name with [`GET /auth/mailbox-availability`](/docs/api/reference/client/authentication/#op-get-api-v1-auth-mailbox-availability): ```bash curl "https://api.coritan.com/api/v1/auth/mailbox-availability?local_part=alex" ``` ```json {"available": true, "reason": "ok", "detail": null, "address": "alex@coritan.gg"} ``` `reason` is `ok`, `invalid`, `reserved` or `taken`, and `detail` says why a name cannot be used. When there is no mailbox to offer, the domain endpoint answers `"enabled": false` and the availability check answers `404` `Mailboxes are not offered on this storefront`. The availability check is rate-limited per IP address. ## API - `POST /api/v1/auth/register`: Register (https://www.coritan.com/docs/api/reference/client/authentication/#op-post-api-v1-auth-register) - `GET /api/v1/auth/mailbox-availability`: Live check behind the sign-up form's username field (https://www.coritan.com/docs/api/reference/client/authentication/#op-get-api-v1-auth-mailbox-availability) - `GET /api/v1/auth/mailbox-domain`: Mailbox domain (https://www.coritan.com/docs/api/reference/client/authentication/#op-get-api-v1-auth-mailbox-domain) - `GET /api/v1/auth/turnstile`: Turnstile config (https://www.coritan.com/docs/api/reference/client/authentication/#op-get-api-v1-auth-turnstile) # The dashboard > What the dashboard overview shows, how the sidebar and search are laid out, and where to find each product and setting. Source: https://www.coritan.com/docs/get-started/dashboard/ In the dashboard: - /dashboard: https://www.coritan.com/dashboard The [dashboard](https://www.coritan.com/dashboard) is where you manage everything on your Coritan account: services, billing, support and settings. It opens on an overview of the account, with a sidebar on the left and a top bar that holds search, your credit balance and the account menu. ## The overview The overview greets you with **Welcome back** and has three buttons at the top: **Open a ticket**, **Add credit** and **Order service**. Under them, four figures summarise the account: Active services : How many of your services are running now. Credit balance : The credit on your account. It pays invoices and hourly services first. See [Add credit to your balance](/docs/billing/add-credit/). Due now : The total of your unpaid invoices. Monthly spend : What your active services cost, with every billing cycle converted to a month. Below the figures, cards list what changed: Needs your attention : Invoices that are overdue or waiting for payment, services that are suspended or failed to set up, services waiting for their first payment, and services whose cancellation is scheduled. Each line opens the invoice or the service. When invoices are unpaid, the card has a **Pay invoice** button. Products : How many services you have of each product. Recent services and Recent invoices : Your latest services and invoices, with **All services** and **All invoices** links to the full lists. A new account also sees a card that offers a free `coritan.gg` name, with a **Claim a name** button. It stays until you [claim one](/docs/proxies/coritan-gg-names/). ## The sidebar The sidebar groups pages by what they manage. It shows only the products that are open to your account. | Group | Items | Where the docs are | | --- | --- | --- | | Overview | **Dashboard**, **Services**, **Order service** | [Manage your services](/docs/get-started/services/), [Order a service](/docs/get-started/order-a-service/) | | Products | **Container Apps**, **Cloud Compute**, **Object Storage**, **Email**, **Websites**, **Edge Proxy** | [Container Apps](/docs/managed-containers/), [Cloud Compute](/docs/cloud-compute/), [Object Storage](/docs/object-storage/), [Email](/docs/mail/), [Websites](/docs/websites/), [Edge Proxy](/docs/proxies/) | | Network | **Floating IPs**, **DDoS Shield**, **Snapshots** | [Floating IPs](/docs/floating-ips/), [DDoS Shield](/docs/ddos-shield/), [Snapshots](/docs/snapshots/) | | Account | **Billing**, **Organizations**, **Support**, **Settings** | [Billing](/docs/billing/), [Organizations](/docs/organizations/), [Support](/docs/support/), [Account and security](/docs/account/) | While Apps is on for the platform, the Products group also has **Apps**, after **Edge Proxy** ([Apps](/docs/apps/)). On a narrow screen the sidebar folds away behind the menu button at the left of the top bar. ## The top bar Search : The field that reads **Search services and pages** opens the search palette. Press Ctrl K, or ⌘ K on a Mac, from anywhere in the dashboard to open it. Webmail and Support : **Webmail** opens [webmail](/docs/mail/webmail/) with the mailboxes your account can open. **Support** opens your [conversations with the Coritan team](/docs/support/conversations/). Credit balance : Your balance, in your billing currency. Select it to open [Billing](/docs/billing/). Account menu : The circle with your initials. It links to **Settings**, **Billing**, **Organizations**, **Webmail**, **Support**, **Documentation** and the coritan.com home page, and holds **Sign out**. ## Search The search palette finds three kinds of things as you type: Services : Your services, by name, product or service ID. Choosing one opens its page. Pages : Every page in the sidebar. Actions : Shortcuts such as **Order a server**, **Order Cloud Compute**, **Add credit**, **Pay invoices**, **Open a ticket**, **Open webmail** and **API keys**. ## Related - [Manage your services](/docs/get-started/services/) - [Sign out and end sessions](/docs/account/sessions/) - [Billing](/docs/billing/) # Order a service > Choose a product, configure it on its own page with any add-ons, pay for the whole order on one invoice, and follow it while we set it up. Source: https://www.coritan.com/docs/get-started/order-a-service/ In the dashboard: - /dashboard/order: https://www.coritan.com/dashboard/order - /dashboard/order/placed/…: https://www.coritan.com/dashboard/order/placed Order anything Coritan sells from the **Order a service** page of the dashboard. Each product has an order page of its own: the plan comes first, then the product's own questions, then its *add-ons*, which are other products you can buy in the same order. One first invoice covers the whole order, and you pay it on the page the order lands on. ## Before you begin - [Create an account](/docs/get-started/create-an-account/) and sign in. - Most accounts pay the first invoice before setup starts. Credit on your account pays it first, so [add credit](/docs/billing/add-credit/) if you want the order to start in one go. You can also pay by card or PayPal once you have placed the order. - Hourly billing needs a minimum total of deposits on your account before you can choose it. [How hourly billing works](/docs/billing/hourly-billing/) explains the rule. ## Choose a product In the sidebar, select **Order service**. The **Order instance**, **Order server** and **Order floating IP** buttons elsewhere in the dashboard open the same order pages. The page lists the products open to your account, each with its starting price. Cloud Compute and Container Apps come first, with their prices by the month and by the hour, and each shows **Sold out** when none of its plans can be ordered right now. Under **Quick start**, four common setups open a compute order with the software already chosen: a Minecraft server, an Ubuntu instance, a Node.js app and a PostgreSQL database. Every other product follows in its category: | Category | Product | Also an add-on for | Guide | | --- | --- | --- | --- | | Compute | Cloud Compute | — | [Create an instance](/docs/cloud-compute/create-an-instance/) | | Compute | Container Apps | — | [Order a server](/docs/managed-containers/order-a-server/) | | Network and security | Floating IPs | Cloud Compute, Container Apps | [Order a floating IP](/docs/floating-ips/order-a-floating-ip/) | | Network and security | DDoS Shield | Cloud Compute, Container Apps, Floating IPs | [Order and use a custom profile](/docs/ddos-shield/custom-profiles/) | | Email | Mail Hosting | Cloud Compute, Container Apps | [Order Mail Hosting or SMTP Relay](/docs/mail/order-a-mail-service/) | | Email | SMTP Relay | Cloud Compute, Container Apps, Mail Hosting | [Order Mail Hosting or SMTP Relay](/docs/mail/order-a-mail-service/) | | Storage | Object Storage | — | [Order Object Storage](/docs/object-storage/order-object-storage/) | | Storage | Snapshot Storage | Container Apps | [Order more snapshot storage](/docs/snapshots/add-snapshot-storage/) | | Edge Proxy | External Server | — | [Connect a server you host elsewhere](/docs/proxies/external-servers/connect-an-external-server/) | Select a card to open the product's order page. When your account holds credit, a line above the cards shows the balance, with **Add credit** beside it. For bare metal, colocation or more than the catalogue lists, select **Open a ticket** at the end of the page and our team sends you a quote. ## Configure an instance or a server The **Cloud Compute** and **Container Apps** pages have five numbered sections: **Plan**, then **Operating system** for an instance or **Software** for a server, then **Location**, **Settings** and **Add-ons**. Every section except **Software** starts with an answer chosen for you, so you can order an instance without opening one. A server needs its software chosen, unless a quick start chose it. 1. Select the **Cloud Compute** or **Container Apps** card. 2. Under **Plan**, choose the billing cycle at the top of the section. Longer terms name what they save against paying monthly. 3. Choose a **Hardware tier** if the page offers more than one, then a plan under **Size**. Each row shows its price for the cycle, its vCPUs, memory and storage. 4. Under **Operating system**, choose a distribution and a release. Under **Software**, choose what the server runs, then its version. 5. Under **Location**, choose a data centre. We start with the one nearest you. A location marked **Sold out** has no room for this plan. 6. Under **Settings**, check the **Hostname** of an instance or the **Server name** of a server. An instance also takes **SSH keys** and the **Public IPv4 address** switch. Answer any **Plan options** too: an option with a price is added to every charge. 7. Under **Add-ons**, turn on anything you want to buy with it, as [Add products to the order](#add-products-to-the-order) describes. 8. Place the order, as [Place the order](#place-the-order) describes. [Create an instance](/docs/cloud-compute/create-an-instance/) and [Order a server](/docs/managed-containers/order-a-server/) describe each section in full. ## Configure any other product Every other order page starts with its plans as cards, each with its price for the billing cycle chosen at the top right. We start with the plan the catalogue recommends, or else the first one on sale, and with the monthly cycle when the plan sells it. The product's own questions follow, such as the region of a floating IP or the domain of a mail service, and then the add-ons it takes. The guide for each product, in the table under [Choose a product](#choose-a-product), walks through its page. ## Add products to the order An add-on is another product bought in the same order. It goes on the order's first invoice and becomes a service of its own, which you can cancel on its own later. The **Add-ons** section lists what the product takes: | On the order page for | You can add | | --- | --- | | Cloud Compute | Up to four floating IPs, a DDoS Shield profile, Mail Hosting and SMTP Relay | | Container Apps | One dedicated IPv4 address, a DDoS Shield profile, Snapshot Storage, SMTP Relay and Mail Hosting | | Floating IPs | A DDoS Shield profile | | Mail Hosting | SMTP Relay | - Each card shows the add-on's price for the order's billing cycle. An add-on that is not sold on that cycle is billed monthly, and its card says so. - Turn on a card's switch to add it. Its fields appear under it, such as how many floating IPs to add, a profile name, a plan or a domain. - A card that cannot be added says why in its place, and its switch stays off. For example, the address pool in the order's location may be sold out, or a DDoS Shield profile may have no address in the order to protect. - Free plans are never add-ons. Order a free plan on its own page. - When the services are ready, we attach the floating IPs or the dedicated address to the new instance or server, and the DDoS Shield profile protects the addresses in the order. ## Place the order The summary under **Your order** sits beside the sections. On a phone it follows them, and **Review order** at the bottom of the screen takes you to it. It lists each choice, the lines we bill under **Billed**, and the **Total** for each billing cycle, with any one-time setup fee, what a longer term saves and what is due today. The line under the total says how the first invoice will be paid. 1. Check the summary. **Change** beside a choice takes you back to its section. 2. If the button is greyed out, read the line under it. It names the first answer still missing, such as `Choose a location.`, and selecting it takes you to that section. 3. If a verification check appears above the button, complete it. It appears on an order whose plan costs nothing. 4. Select the button. Its words follow the first payment: - **Place order and pay** when you pay an invoice after placing the order. - **Order for free** when nothing in the order costs anything. - **Deploy instance**, **Deploy server** or **Place order** when your credit pays the first invoice, or when your account is billed in arrears. If we refuse the order, **Could not place the order** appears above the button with the reason, and your choices stay as they were. Otherwise the order's own page opens, as [Pay and follow the order](#pay-and-follow-the-order) describes. ## How the first payment works When you place an order, we raise one first invoice for everything in it: one billing period of the plan (one hour on hourly billing), the first period of each add-on and any setup fees. The summary shows which of these cases applies before you place the order: **Paid from your credit balance** : Your credit covers the first invoice. We take it from your balance when you place the order, and setup starts straight away. **Pay on the next page** : Your credit does not cover the first invoice. We apply the credit you have, and the order's page asks for the rest. Setup starts once it is paid. **Billed to your account** : Your account is billed in arrears. Setup starts when you place the order, and the first invoice follows at the end of the period. Coritan sets an account up this way, and you cannot choose it. Hourly, daily and weekly prices are paid in advance on every account. **Nothing to pay** : Nothing in the order costs anything. Setup starts when you place it. An order whose plan costs $0 needs the verification check, even when its add-ons cost money, and each account can hold one free service of each kind. The Cloud Compute and Container Apps pages never show the check, and coritan.com does not sell the free Container Apps plan. While an invoice is unpaid, we keep trying to take it from your credit and your saved payment method. We cancel a Container Apps or floating IP order whose first invoice is still unpaid after two days, by default, so pay it soon after ordering. ## Pay and follow the order The order's page shows three steps along the top: **Order placed**, the payment, then **Setting up**. **What you ordered** lists the service and each add-on with its status and price, and **What happens next** says what to do with each one once it is ready. When the title reads **Order placed, payment due**, pay in the page: 1. Under **Pay invoice**, check the amount under **Due now**. 2. Choose how to pay. **Account credit** and your saved cards and PayPal accounts are under **On your account**, and a new card or PayPal under **Pay another way**. 3. Select the button under the choices. It reads **Pay with credit** when your credit covers the amount and **Apply credit** when it covers part of it. For a card, it names the amount. A new card opens a card form in the page, where you select **Pay now**, and PayPal shows its own buttons. A provider with a checkout page of its own sends you there and brings you back afterwards. Once the payment goes through, the page shows **Paid** and moves on to **Setting up**. It checks every few seconds while we set the order up, and the title becomes **Your order is ready** when every service is active. A button at the top of the page then opens the new service, and **Order something else** takes you back to the catalogue. When nothing is due, the page shows **Paid** with what your credit paid, or **Nothing to pay now** for an order that is free or billed in arrears, and setup has already started. To pay later, leave the page and pay the invoice under [Invoices](/docs/billing/invoices/). Setup starts on its own once it is paid. ## Result Every service in the order appears on the [Services](/docs/get-started/services/) page with the status `pending`. It moves to `provisioning` while we set it up and to `active` when it is ready. The first invoice is under [Invoices](/docs/billing/invoices/), with a line for each service in the order and one for each setup fee. ## Troubleshooting The button under the summary is greyed out : The line under it names what is missing, such as `Choose a location.` Select it to go to that section. An add-on's switch is greyed out : The card says why under its description. Change what it names, such as the location or the public IPv4 switch, or order the product on its own page later. `Hourly billing needs a deposit first.` : Your account has not deposited enough yet to use hourly billing. The **Size** card says how much more it needs. Select **Top up credit** to add it, or choose another cycle. `Every location is sold out for this plan` : No data centre has room for this size on this tier right now. Capacity differs by plan, so try another size or hardware tier. `That location has no capacity for this plan.` : The location filled up after you chose it. Choose another under **Location**. **Could not load the prices** : The list of products did not load. Select **Try again**. `Complete the verification check.` : The plan costs nothing, so the order needs the check above the button. Complete it, then place the order. `The verification check did not pass. Complete it and try again.` : The check expired or failed. Complete it again, then place the order again. A message that ends `Upgrade or remove one to create another.` : You already hold the one free service of this kind that an account can have. Upgrade it or cancel it before you order another. `Too many requests for this action. Please wait and try again.` : Your account placed too many free orders in a short time. Wait ten minutes and try again. The order's page shows an order you placed earlier : You placed the same order in the last two minutes, and it is still waiting to start. We show that order again instead of creating a second one. To order a second service on the same plan, give it a different name. `Payment cancelled; the invoice is still due.` : You came back from a checkout page without paying. Choose a way to pay again under **Pay invoice**. **Could not load the invoice** : The order is placed, but its invoice did not load on the order's page. Pay it under [Invoices](/docs/billing/invoices/) instead. The order's page still says **Setting up** : The page stops checking after a few minutes. Reload it, or follow the service on the [Services](/docs/get-started/services/) page. ## Related - [Manage your services](/docs/get-started/services/) - [Pay an invoice](/docs/billing/invoices/) - [Change a service's plan](/docs/billing/change-plan/) - [Data centres and locations](/docs/platform/data-centres/) ## With the API The catalogue needs no authentication. List the products with [`GET /products/`](/docs/api/reference/client/catalog/#op-get-api-v1-products). It returns up to `per_page` products (default 100, at most 200) from `page`, and `group` or `hardware_tier` (a tier's slug) narrow the list. It leaves out the free Container Apps plan, which coritan.com does not sell: ```bash curl "https://api.coritan.com/api/v1/products/?per_page=200" ``` Each product carries what an order needs: `id` : The `product_id` to order. `module_name` : The kind of product: `vps` for Cloud Compute and `container` for Container Apps. The others are `ip`, `antiddos`, `mail`, `smtp_relay`, `object_storage`, `storage` (Snapshot Storage) and `external_server`. `pricing` : One entry per billing cycle. Its `id` is the `pricing_id` to order, with `billing_cycle`, `price`, `setup_fee`, `currency` and `is_active`. Only an active entry can be ordered. `config_options` : The plan's options, which the dashboard shows under **Plan options**. Put an option's `field_name` in `config` to take it. Its `price_modifier` is added to each charge. `is_orderable_now`, `stock_status` and `availability_reason` : Whether the product can be ordered anywhere right now. `stock_status` is `in_stock` or `out_of_stock`. `locations` : For compute products, each location's `code`, `name` and `country_code`, whether it is `orderable` for this product, and a `reason` when it is not. [`GET /products/{product_id}`](/docs/api/reference/client/catalog/#op-get-api-v1-products-product-id) returns one product in the same shape. [`GET /locations`](/docs/api/reference/client/catalog/#op-get-api-v1-locations) lists the active locations with their airport `code`, `name`, `country_code` and `timezone`. [`GET /locations/availability`](/docs/api/reference/client/catalog/#op-get-api-v1-locations-availability) checks capacity per location for a size: it takes `module` (`vps` or `container`), and optionally `hardware_tier_id`, `memory_mb`, `disk_gb` with `template_id` for an instance, or `disk_mb` for a server. Each location in the answer has `available`, and the `reason` `No eligible capacity` when it is `false`. Place the order with [`POST /services/order`](/docs/api/reference/client/services/#op-post-api-v1-services-order). This one needs your access token. The example orders an instance with two floating IPs and a DDoS Shield profile: ```bash curl -X POST https://api.coritan.com/api/v1/services/order \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "product_id": 7, "pricing_id": 21, "hostname": "web-1.example.com", "idempotency_key": "3f1c9a52-8e0d-4d6b-b7a4-5c2e9f0a1d36", "config": { "location": "fra", "template_id": 4, "hostname": "web-1.example.com", "order_ipv4": true }, "addons": [ {"product_id": 31, "quantity": 2}, {"product_id": 44, "config": {"profile_name": "web-1"}} ] }' ``` `product_id` and `pricing_id` : The product and one of its active prices. `hostname` : Optional. The name the service is listed under, up to 100 characters. `config` : The product's settings. A compute order needs `location`, an airport code such as `fra`. [Create an instance](/docs/cloud-compute/create-an-instance/) and [Order a server](/docs/managed-containers/order-a-server/) list the other keys for each product. `addons` : Optional. Up to 8 products bought with this one, as [Add-ons in the order](#add-ons-in-the-order) describes. `idempotency_key` : Up to 64 characters. A floating IP order needs one, and a retry with the same key returns the first order. [Idempotent requests](/docs/api/idempotency/) explains how to use it. `turnstile_token` : Only for an order whose plan costs $0, when [`GET /auth/turnstile`](/docs/api/reference/client/authentication/#op-get-api-v1-auth-turnstile) answers `"enabled": true`. It is needed even when the order's add-ons cost money. The token comes from the verification widget in a browser, so place such orders on the website. ### Add-ons in the order Each entry in `addons` takes these fields: `product_id` : The add-on product. [Add products to the order](#add-products-to-the-order) lists the products each product takes. `pricing_id` : Optional. One of the add-on's active prices. Without it, we take its price on the order's billing cycle, else its monthly price, else its first active price. An hourly price needs the same deposit as an hourly service. `quantity` : Optional, `1` when left out, at most `8`. Only a floating IP takes more than 1, and each one becomes a service of its own. An instance takes up to 4 floating IPs, and a server takes one `/32` address. `config` : Optional. The settings an order of that product on its own takes. A floating IP comes from a pool in the order's `location` unless you name one in `pool_id`, and takes only the prefix length its product sells. A DDoS Shield profile takes `profile_name` (up to 40 characters, the order's `hostname` when left out), `protection_mode`, `default_action`, `per_source_pps` and `aggregate_pps`. It protects the addresses in the order, so leave `ip_service_ids` out. An instance takes floating IPs only with `"order_ipv4": true` in its `config`, because the included address stays its free primary address. A DDoS Shield profile needs an address in the order: an instance's included IPv4, a floating IP added to a server, or the floating IP being ordered. When we cannot take one add-on, we refuse the whole order and create nothing. A new order answers `201`: ```json { "service": {"id": 1042, "status": "pending", "billing_cycle": "monthly", "amount": 12.0, "hostname": "web-1.example.com"}, "addons": [ {"id": 1044, "status": "pending", "billing_cycle": "monthly", "amount": 3.0}, {"id": 1045, "status": "pending", "billing_cycle": "monthly", "amount": 3.0}, {"id": 1046, "status": "pending", "billing_cycle": "monthly", "amount": 5.0} ], "invoice_id": 5531, "requires_payment": true, "checkout_available": true, "amount_due": "23.00", "message": "Invoice created; pay to start provisioning" } ``` `service` is the new service, in the shape [Manage your services](/docs/get-started/services/#with-the-api) describes. `addons` holds a service for each add-on, one per unit of `quantity`, in the order you listed them. An instance's included IPv4 is a service of its own too, but it is not in `addons`. When `requires_payment` is `true`, pay `invoice_id` as [Pay an invoice](/docs/billing/invoices/#with-the-api) shows; setup starts once it is paid. `message` says what happened: | `message` | Meaning | | --- | --- | | `Payment applied from credit balance; provisioning started` | Credit paid the first invoice. | | `Invoice created; pay to start provisioning` | `amount_due` is still to pay. | | `Provisioning started` | Nothing was due first, because the order is free or the account is billed in arrears. | | `Provisioning started (floating IP will auto-attach)` | As above, for an instance ordered with its included IPv4. | | `Order already submitted` | The same product, price and `hostname` were ordered in the last two minutes and are still `pending`. You get that order back, with its add-ons. | | `Replayed existing IP order` | A floating IP order was sent again with the same `idempotency_key`. | The errors you can act on: - `404` `Product not found` or `Pricing tier not found`: the id is wrong, or the price is not active. - `403` `You must deposit at least $10 before using hourly billing services`: the account cannot use hourly billing yet. [`GET /billing/hourly-eligibility`](/docs/api/reference/client/billing/#op-get-api-v1-billing-hourly-eligibility) returns the deposit it still needs in `remaining`. - `403` with `"error": "plan_not_sold_here"`: the free Container Apps plan cannot be ordered here. - `403` with `"error": "turnstile_failed"`: the verification check was missing or did not pass on an order whose plan costs $0. - `409` with `"error": "free_limit_reached"`: the account already holds a free service of this kind. - `422` with `{"detail": {"errors": [...]}}`: `config` is incomplete, the location has no room, or an add-on cannot go on this order. Examples are `location is required (airport code, e.g. iad)`, `Keep the included public IPv4 to add floating IPs. It is free, and floating IPs are extra addresses on top of it.` and `A server takes one floating IP, so add at most one.` - `422` for a floating IP, ordered on its own or as an add-on, with a `prefix_len` its product does not sell. The message ends `Leave prefix_len out or set it to 29.` for a product that sells `/29` subnets. - `422` `idempotency_key is required for IP orders`, and `409` `An identical IP order is already in progress; retry in a moment` when the first request with that key has not finished. - `429` with `"error": "rate_limited"`: more than 12 free orders from the account in ten minutes. [Rate limits](/docs/api/rate-limits/) lists every limit. ## API - `POST /api/v1/services/order`: Order a platform service, and any add-ons bought with it (https://www.coritan.com/docs/api/reference/client/services/#op-post-api-v1-services-order) - `GET /api/v1/products/`: List products (https://www.coritan.com/docs/api/reference/client/catalog/#op-get-api-v1-products) - `GET /api/v1/products/{product_id}`: Get product (https://www.coritan.com/docs/api/reference/client/catalog/#op-get-api-v1-products-product-id) - `GET /api/v1/locations`: List locations (https://www.coritan.com/docs/api/reference/client/catalog/#op-get-api-v1-locations) - `GET /api/v1/locations/availability`: Locations availability (https://www.coritan.com/docs/api/reference/client/catalog/#op-get-api-v1-locations-availability) # Manage your services > Find every service on your account, filter the list, and read a service's status, configuration and history. Source: https://www.coritan.com/docs/get-started/services/ In the dashboard: - /dashboard/services: https://www.coritan.com/dashboard/services - /dashboard/services/…/overview: https://www.coritan.com/dashboard/services - /dashboard/services/…/billing: https://www.coritan.com/dashboard/services A *service* is one thing you pay for: an instance, a server, a floating IP, a mail service, a bucket plan and so on. The **Services** page lists every service on your account, from every product, with its status, its next renewal and its price. ## Before you begin - Sign in to the [dashboard](https://www.coritan.com/dashboard). The list holds the services billed to your account. ## Find a service 1. In the sidebar, select **Services**. 2. Type part of a name, an address or a service ID in **Search by name, address or ID**. 3. To see one status group, select **Active**, **Pending**, **Suspended** or **Ending**. **All** shows every service again. 4. If you have more than one product, choose one in the product menu. **All products** shows them all. 5. To show only services with a tag, select the tag in the row of tags under the search box. Select more than one tag to show services that have all of them. **Clear** turns the tag filter off. 6. To sort the list, select a column heading. 7. Select a service to open it. | Filter | Shows | | --- | --- | | **Active** | Services that are running and paid up. | | **Pending** | Services waiting for their first payment, and services we are setting up. | | **Suspended** | Services we paused, usually for an unpaid invoice. | | **Ending** | Services with a cancellation scheduled, and services that have ended. | Each row shows the service's name, with its address and tags under it and an **Ending** badge when a cancellation is scheduled. The other columns are **Product**, **Status**, **Renews** (the next renewal date, or `Hourly`) and **Price**. ## Read a service's page A floating IP, a mail service, an Object Storage service and the other products with an area of their own open on that product's page. Any other service opens a service page with two tabs, **Overview** and **Billing**. When the product has its own page, the service page has a button at the top that opens it there. ### The Overview tab **Summary** : The status, the product, the price, the next renewal, when you ordered it (**Ordered**), when setup finished (**Set up**), its address and its **Service ID**. **Configuration** : What you chose when you ordered, such as the location or the image. Values that look like passwords, secrets or tokens are left out. **Details** : Technical details that setup recorded, such as the address a server got. **Tags** : Your labels for the service. [Tag your services](/docs/get-started/tags/) explains them. A box at the top explains a service that needs something from you: **Waiting for payment** : The first invoice is unpaid. Select **Open invoices** and [pay it](/docs/billing/invoices/), and setup starts on its own. **Suspended** : The box gives the reason. [Failed payments and suspended services](/docs/billing/failed-payments/) explains how to restore the service. **Cancellation scheduled** : The service ends on the date shown. To keep it, select **Open a ticket** and ask before that date. ### The Billing tab **Current plan** : The product, the plan, the price and the status. It also shows the next renewal, or for an hourly service what it has cost this month (**Used this month**) against its monthly cap. **Change plan** moves the service to another plan, as [Change a service's plan](/docs/billing/change-plan/) describes. **Activity** : The service's history, newest first: **When** it happened, the **Event**, the **Details** and **By** whom. **Cancel service** : Ends the service. [Cancel a service](/docs/billing/cancel-a-service/) explains the choices. Most product pages have the same **Billing** tab, so you can change the plan or cancel from there too. ## Result You can see where each service stands and what it costs. A service moves through these statuses: | Status | Meaning | | --- | --- | | `pending` | Ordered, and waiting for its first payment or for setup to start. | | `provisioning` | We are setting it up. | | `active` | Ready to use. | | `suspended` | Paused, usually for an unpaid invoice. We keep its data while it is suspended. | | `pending_termination` | Cancelled, and being removed. | | `terminated` | Ended and removed. | | `failed` | Setup did not finish. Contact [support](/docs/support/). | ## Troubleshooting **No services yet** : The account has no services. Select **Browse products** to [order one](/docs/get-started/order-a-service/). **Could not load your services** : The list did not load. Select **Try again**. A service stays in `pending` : Its first invoice is unpaid. Open **Billing**, then **Invoices**, and pay it. If it is paid and the service has not moved, [contact support](/docs/support/conversations/). A service shows `failed` : Setup could not finish. Open a ticket with the service ID so the team can set it up again or cancel it. ## Related - [Order a service](/docs/get-started/order-a-service/) - [Tag your services](/docs/get-started/tags/) - [Pay an invoice](/docs/billing/invoices/) - [Cancel a service](/docs/billing/cancel-a-service/) ## With the API List your services with [`GET /services/`](/docs/api/reference/client/services/#op-get-api-v1-services), newest first: ```bash curl "https://api.coritan.com/api/v1/services/?status=active&resource_type=vps&limit=50" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` `status` : One status from the table above. `resource_type` : The product: `vps` (or `cloud_compute`), `container`, `ip` (or `floating_ip`), `mail` or `smtp_relay`. Another word answers `422` with `"error": "invalid_resource_type"` and the words it accepts in `allowed`. `tag` : A tag on the service, such as `production`. `limit` and `offset` : The page size (default 100, at most 500) and how many services to skip. [Pagination and filtering](/docs/api/pagination/) explains paging. Each service has these fields: ```json { "id": 1042, "product_id": 7, "pricing_id": 21, "hostname": "web-1.example.com", "status": "active", "billing_cycle": "monthly", "amount": 12.0, "next_due_date": "2026-10-16T09:30:00", "product_name": "Cloud Compute 2 GB", "module_name": "vps", "pricing_name": "Monthly", "termination_date": null, "tags": ["production"], "resource": {"type": "vps", "label": "web-1.example.com", "status": "running", "address": "203.0.113.10", "detail": {}} } ``` `amount` is the price per cycle, and `next_due_date` the next renewal. `termination_date` is set once a cancellation is scheduled. `config` holds what you ordered, `module_data` what setup recorded, and `suspension_reason` why a service is `suspended`. For an hourly service, `hourly_usage_this_month`, `monthly_cap` and `hourly_cap_reached` show its spend against the cap. `resource` summarises what the service runs: its `type` (`vps`, `container`, `ip` or `external_server`), `label`, `status` and `address`, with product details in `detail`. Get one service with [`GET /services/{service_ref}`](/docs/api/reference/client/services/#op-get-api-v1-services-service-ref). `service_ref` is the service ID, or the UUID of the instance, server or floating IP behind it. A service that does not exist answers `404` `Service not found`, and someone else's answers `403` `Access denied`. Read its history with [`GET /services/{service_ref}/events`](/docs/api/reference/client/services/#op-get-api-v1-services-service-ref-events), newest first. It takes `page` and `limit` (default 50, at most 200). Each event has `event_type`, `description`, `actor_type` (such as `user` or `system`), `metadata` and `created_at`. The event types are `created`, `provisioning_started`, `provisioned`, `provisioning_failed`, `suspended`, `unsuspended`, `upgrade`, `downgrade`, `termination_requested`, `terminated`, `action_executed` and `config_changed`. [`POST /services/{service_ref}/actions`](/docs/api/reference/client/services/#op-post-api-v1-services-service-ref-actions) runs an action the product offers on an `active` service, with `action` and optional `params`: ```bash curl -X POST https://api.coritan.com/api/v1/services/1042/actions \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"action": "reboot"}' ``` ```json {"success": true, "message": "Power action 'reboot' executed", "data": null} ``` | Product | `action` values | | --- | --- | | Cloud Compute | `start`, `stop`, `shutdown`, `reboot`, `console`, `reset_password`, `rebuild`, `create_snapshot`, `enter_rescue`, `exit_rescue`, `regenerate_cloud_init`, `set_hostname`, `set_ssh_keys` | | Container Apps | `start`, `stop`, `restart`, `kill`, `console`, `reinstall` | | DDoS Shield profile | `view_alerts`, `whitelist_ip`, `blacklist_ip`, `bind_ip`, `unbind_ip` | | External Server | `update_upstream`, `set_proxy_protocol`, `set_region`, `test_connection` | Each product's section documents its own endpoints, which do the same work with that product's checks and limits: [Cloud Compute](/docs/cloud-compute/), [Container Apps](/docs/managed-containers/), [DDoS Shield](/docs/ddos-shield/custom-profiles/) and [external servers](/docs/proxies/external-servers/). Use those for day-to-day work. Floating IPs, Mail Hosting and SMTP Relay answer `"success": false` with the endpoints to use instead. A service that is not `active` answers `400` `Service is not active`, and an action the product does not offer answers `400` `Action 'name' not available`. ## API - `GET /api/v1/services/`: List services (https://www.coritan.com/docs/api/reference/client/services/#op-get-api-v1-services) - `GET /api/v1/services/{service_ref}`: Fetch a service by numeric id OR underlying resource UUID (instance/server/IP) (https://www.coritan.com/docs/api/reference/client/services/#op-get-api-v1-services-service-ref) - `GET /api/v1/services/{service_ref}/events`: Get events (https://www.coritan.com/docs/api/reference/client/services/#op-get-api-v1-services-service-ref-events) - `POST /api/v1/services/{service_ref}/actions`: Execute action (https://www.coritan.com/docs/api/reference/client/services/#op-post-api-v1-services-service-ref-actions) # Tag your services > Add labels to your services so you can filter and find them, in the dashboard or through the API. Source: https://www.coritan.com/docs/get-started/tags/ A *tag* is a label of your own, such as `production` or `eu-customers`, that you put on a service, an instance or a server to find it again. Tags do nothing else: they change no setting and cost nothing. ## Before you begin - A tag uses lowercase letters, digits and hyphens, and starts with a letter or a digit. The dashboard turns spaces into hyphens and capitals into lowercase for you. - Tags on a service and tags on the instance or server behind it are kept apart. [Where tags live](#where-tags-live) explains which list each one filters. ## Add a tag 1. Open the thing you want to tag: - A service: its page from **Services**, or the page of a floating IP, a mail service or an Object Storage service. The **Tags** card is on the **Overview** tab. - A Cloud Compute instance or a Container Apps server: its **Settings** tab, in the **Tags** card. 2. Type the tag in **Add a tag**. Under the field, **Suggestions** offers the tags you already use. 3. Press Enter, type a comma, or select **Add tag**. The tag is saved at once and appears as a chip above the field. ## Remove a tag 1. Open the **Tags** card that holds the tag. 2. Select the cross on the tag's chip. With the field empty, Backspace removes the last tag. ## Filter a list by tag 1. Open **Services**, **Cloud Compute** or **Container Apps**. 2. Select a tag in the row of tags under the search box. The number beside each tag counts the items that have it. 3. Select more tags to narrow the list to items that have all of them. 4. Select **Clear** to show everything again. ## Where tags live A Cloud Compute instance and a Container Apps server each have a service as well: the service carries the plan and the billing. Each keeps its own tags. | Where you add the tag | Where it shows and filters | | --- | --- | | A service's **Overview** tab | **Services** | | An instance's **Settings** tab | **Cloud Compute** | | A server's **Settings** tab | **Container Apps** | To find an instance or a server from **Services**, tag its service as well. ## Result The tag shows on the item and in its list, where it filters. Through the API, `GET /services/?tag=` returns only the services with that tag. ## Troubleshooting **Could not save the tags** with `Tag must be 1–128 chars: lowercase letters, digits, hyphen, underscore` : The tag has a character that tags cannot hold, such as a dot, a colon or a slash. Use lowercase letters, digits and hyphens. A tag you added on an instance or server is missing from **Services** : Instance and server tags filter **Cloud Compute** and **Container Apps**. Add the tag on the service's **Overview** tab too, as [Where tags live](#where-tags-live) shows. ## Related - [Manage your services](/docs/get-started/services/) - [Pagination and filtering](/docs/api/pagination/) ## With the API Each tag belongs to a *source*: a type and an ID. | `source_type` | `source_id` | | --- | --- | | `service` | The service ID, such as `1042`. | | `vps` (or `cloud_compute`) | The instance's UUID. | | `container_server` (or `server`) | The server's UUID. | | `dns_zone` | The DNS zone's ID. | | `proxy_route` | The web proxy's ID. | Read a source's tags with [`GET /client/tags/sources/{source_type}/{source_id}`](/docs/api/reference/client/resource-tags/#op-get-api-v1-client-tags-sources-source-type-source-id): ```bash curl https://api.coritan.com/api/v1/client/tags/sources/service/1042 \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json {"source_type": "service", "source_id": "1042", "tags": ["production", "web"]} ``` Add one tag with [`POST`](/docs/api/reference/client/resource-tags/#op-post-api-v1-client-tags-sources-source-type-source-id) and `{"tag": "production"}`. Replace every tag at once with [`PUT`](/docs/api/reference/client/resource-tags/#op-put-api-v1-client-tags-sources-source-type-source-id) and a list of up to 50; an empty list removes them all: ```bash curl -X PUT https://api.coritan.com/api/v1/client/tags/sources/vps/0b6f3e2a-7c41-4d8e-9f25-6a1d3c8e4b70 \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"tags": ["production", "eu-customers"]}' ``` Remove one with [`DELETE /client/tags/sources/{source_type}/{source_id}/{tag}`](/docs/api/reference/client/resource-tags/#op-delete-api-v1-client-tags-sources-source-type-source-id-tag). All three answer with the source's tags after the change, sorted: `{"tags": ["eu-customers", "production"]}`. The API lowercases a tag and turns spaces into hyphens. It accepts lowercase letters, digits, hyphens and underscores, 1–128 characters, starting with a letter or a digit. Adding a tag the source already has changes nothing. [`GET /client/tags/vocabulary`](/docs/api/reference/client/resource-tags/#op-get-api-v1-client-tags-vocabulary) lists the tags you use on one `source_type`, with how many of your resources carry each: ```bash curl "https://api.coritan.com/api/v1/client/tags/vocabulary?source_type=service" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json {"items": [{"tag": "production", "resource_count": 3}, {"tag": "web", "resource_count": 1}], "source_type": "service"} ``` The errors: - `400` `Unsupported source_type: ...` or `Tagging not enabled for source_type: ...`: use a type from the table. - `404` `Service not found`, `Cloud Compute instance not found`, `Server not found`, `DNS zone not found` or `Proxy route not found`: the ID is wrong or the item is not yours. - `400` `Tag must be 1–128 chars: lowercase letters, digits, hyphen, underscore`: the tag breaks the format. - `422`: a `PUT` sent more than 50 tags. ## API - `GET /api/v1/client/tags/sources/{source_type}/{source_id}`: Get resource tags (https://www.coritan.com/docs/api/reference/client/resource-tags/#op-get-api-v1-client-tags-sources-source-type-source-id) - `POST /api/v1/client/tags/sources/{source_type}/{source_id}`: Add resource tag (https://www.coritan.com/docs/api/reference/client/resource-tags/#op-post-api-v1-client-tags-sources-source-type-source-id) - `PUT /api/v1/client/tags/sources/{source_type}/{source_id}`: Replace resource tags (https://www.coritan.com/docs/api/reference/client/resource-tags/#op-put-api-v1-client-tags-sources-source-type-source-id) - `DELETE /api/v1/client/tags/sources/{source_type}/{source_id}/{tag}`: Remove resource tag (https://www.coritan.com/docs/api/reference/client/resource-tags/#op-delete-api-v1-client-tags-sources-source-type-source-id-tag) - `GET /api/v1/client/tags/vocabulary`: Distinct tags on resources you own for a given sourcetype (https://www.coritan.com/docs/api/reference/client/resource-tags/#op-get-api-v1-client-tags-vocabulary) # Make your first API request > Get an access token, list your services and refresh the token when it expires, with curl commands you can copy. Source: https://www.coritan.com/docs/get-started/first-steps-with-the-api/ This tutorial takes you from your email and password to a working API session in a terminal. You sign in, read your account, list your services and renew the access token when it runs out. Every request goes to `https://api.coritan.com/api/v1`. ## Before you begin - A Coritan account with a password. [Create an account](/docs/get-started/create-an-account/) if you have none. - `curl`, and `jq` to read values out of the JSON answers. - Your authenticator app, if you turned on [two-factor authentication](/docs/account/two-factor-authentication/). > [!IMPORTANT] > The API accepts the access token that signing in returns. It does not accept the keys from **Settings**, **API keys** (they start with `ct_`): a request that sends one answers `401`. [Manage API keys](/docs/account/api-keys/) explains what the keys are for today. ## 1. Check whether sign-in needs a challenge The sign-in page can ask for a bot check. Ask the API whether it is on: ```bash curl https://api.coritan.com/api/v1/auth/turnstile ``` ```json {"enabled": false, "site_key": ""} ``` When `enabled` is `false`, an email and a password are enough to sign in, and you can go on to the next section. When it is `true`, `POST /auth/login` also needs a `turnstile_token` that only the check on the [sign-in page](https://www.coritan.com/login) produces. Without one it answers `403` with `{"detail": {"error": "turnstile_failed", "message": "Verification required"}}`, so a script cannot sign in on its own. You now know whether your password is enough to sign in from a terminal. ## 2. Sign in and keep the tokens 1. Read your password into a variable, so it stays out of your shell history: ```bash read -rs -p "Password: " CORITAN_PASSWORD; echo ``` 2. Sign in with [`POST /auth/login`](/docs/api/reference/client/authentication/#op-post-api-v1-auth-login). `jq` builds the JSON body, so a password with quotes or other special characters is sent intact: ```bash curl -s -X POST https://api.coritan.com/api/v1/auth/login \ -H "Content-Type: application/json" \ -d "$(jq -n --arg email alex@example.com --arg password "$CORITAN_PASSWORD" '{email: $email, password: $password}')" \ > login.json ``` Without two-factor authentication, `login.json` holds your tokens: ```json { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "bearer", "expires_in": 1800 } ``` `expires_in` is how many seconds the access token lasts. 3. If you use two-factor authentication, the answer asks for your code instead: ```json {"mfa_required": true, "mfa_setup_required": false, "mfa_token": "eyJhbGciOiJIUzI1NiIs...", "expires_in": 600, "token_type": "bearer"} ``` Send the code from your app with [`POST /auth/mfa/verify`](/docs/api/reference/client/authentication-mfa/#op-post-api-v1-auth-mfa-verify) within ten minutes, with `mfa_token` as the bearer token. A recovery code works too, once. The answer holds the same tokens as a sign-in without a second factor: ```bash MFA_TOKEN=$(jq -r .mfa_token login.json) curl -s -X POST https://api.coritan.com/api/v1/auth/mfa/verify \ -H "Authorization: Bearer $MFA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"code": "123456"}' \ > login.json ``` 4. Keep both tokens in variables: ```bash export CORITAN_TOKEN=$(jq -r .access_token login.json) CORITAN_REFRESH=$(jq -r .refresh_token login.json) ``` `echo "$CORITAN_TOKEN"` now prints a long string that starts with `eyJ`. Keep `login.json` private or delete it: anyone with these tokens can use your account. ## 3. Read your account Send the access token in the `Authorization` header of every request. [`GET /auth/me`](/docs/api/reference/client/authentication/#op-get-api-v1-auth-me) returns the account the token belongs to: ```bash curl -s https://api.coritan.com/api/v1/auth/me \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "id": 4821, "email": "alex@example.com", "first_name": "Alex", "last_name": null, "company": null, "status": "active", "billing_mode": "prepaid", "credit_balance": 25.0, "currency": "USD", "country_code": "GB", "currency_source": "default", "created_at": "2026-09-01T10:15:00" } ``` The `email` is yours, which shows the token works. [Update your profile](/docs/account/profile/#with-the-api) describes each field. ## 4. List your services [`GET /services/`](/docs/api/reference/client/services/#op-get-api-v1-services) returns a JSON array of the services on your account, newest first. This prints one line for each of the first five: ```bash curl -s "https://api.coritan.com/api/v1/services/?limit=5" \ -H "Authorization: Bearer $CORITAN_TOKEN" \ | jq -c '.[] | {id, hostname, status, product_name}' ``` ```json {"id":1042,"hostname":"web-1.example.com","status":"active","product_name":"Cloud Compute 2 GB"} ``` An account with no services prints nothing. You see the same services as on the [Services page](https://www.coritan.com/dashboard/services). [Manage your services](/docs/get-started/services/#with-the-api) lists the filters and fields. ## 5. Renew the access token When the access token runs out, every request answers `401` with `Invalid or expired token`. Exchange the refresh token for a new pair with [`POST /auth/refresh`](/docs/api/reference/client/authentication/#op-post-api-v1-auth-refresh). It needs no password, no bot check and no second factor: ```bash curl -s -X POST https://api.coritan.com/api/v1/auth/refresh \ -H "Content-Type: application/json" \ -d "$(jq -n --arg token "$CORITAN_REFRESH" '{refresh_token: $token}')" \ > login.json export CORITAN_TOKEN=$(jq -r .access_token login.json) CORITAN_REFRESH=$(jq -r .refresh_token login.json) ``` The answer has the same fields as a sign-in. Keep the new `refresh_token` each time: it starts a full lifetime of its own, while the one you sent keeps its old expiry. When a refresh token has expired too, sign in again as in section 2. Run the `GET /auth/me` request from section 3 again. It answers with your account, using the new token. ## Troubleshooting `401` `Invalid email or password` : The email or the password is wrong. The answer is the same for both. `403` `Account is suspended or closed` : The account cannot sign in. [Contact support](/docs/support/). `401` `That code is not right` : The code is wrong, or you used it already: each code from the app works once. Wait for the next one. `401` `Invalid or expired token` from `/auth/mfa/verify` : The `mfa_token` is more than ten minutes old. Sign in again. `401` with `"error": "mfa_required"` : You sent the `mfa_token` to another endpoint. Finish section 2 first. `401` `Token invalidated by password change` : Someone changed the account's password after the token was issued. Sign in again with the new password. `403` `Not authenticated` : The request has no `Authorization` header. Check that `$CORITAN_TOKEN` is set in the shell you are using. `429` `Too many authentication attempts. Please try again later.` : Too many sign-in or refresh attempts failed from your address. Wait a minute, as the `Retry-After` header says. ## Related - [Authentication](/docs/api/authentication/) covers the token rules and the other kinds of credential. - [Errors](/docs/api/errors/) and [Rate limits](/docs/api/rate-limits/) describe what the API answers when a request fails. - [Sign out and end sessions](/docs/account/sessions/) explains how to stop a token working. - The [Client API reference](/docs/api/reference/client/services/) lists every endpoint. [api.coritan.com/docs](https://api.coritan.com/docs) lets you try them in the browser. ## API - `GET /api/v1/auth/turnstile`: Turnstile config (https://www.coritan.com/docs/api/reference/client/authentication/#op-get-api-v1-auth-turnstile) - `POST /api/v1/auth/login`: Login (https://www.coritan.com/docs/api/reference/client/authentication/#op-post-api-v1-auth-login) - `POST /api/v1/auth/mfa/verify`: Second step of signing in (https://www.coritan.com/docs/api/reference/client/authentication-mfa/#op-post-api-v1-auth-mfa-verify) - `GET /api/v1/auth/me`: Get me (https://www.coritan.com/docs/api/reference/client/authentication/#op-get-api-v1-auth-me) - `GET /api/v1/services/`: List services (https://www.coritan.com/docs/api/reference/client/services/#op-get-api-v1-services) - `POST /api/v1/auth/refresh`: Refresh (https://www.coritan.com/docs/api/reference/client/authentication/#op-post-api-v1-auth-refresh) # Use these docs with LLMs and AI agents > Give an AI assistant every Coritan guide as plain Markdown, through llms.txt, llms-full.txt, a page's index.md or the search API. Source: https://www.coritan.com/docs/get-started/for-llms/ Every page on this site is also plain Markdown, so a large language model (LLM) or an AI agent can read the same guides you do, without the site's layout around them. Point your assistant at one of the files below, and it can answer questions about Coritan and walk you through a task with the exact labels and API calls. ## The files [`/docs/llms.txt`](/docs/llms.txt) : The index. It links every guide on this site, section by section, each with one line on what it covers, and names the files below. Give an agent this address first: it can then fetch only the pages it needs. [`/docs/llms-full.txt`](/docs/llms-full.txt) : Every guide in one file, in the order the sidebar shows them. It is large (more than 2 MB), so use it with an assistant that can take a long context, or pick a section's file instead. `/docs/
/llms-full.txt` : One section in one file, such as [`/docs/mail/llms-full.txt`](/docs/mail/llms-full.txt) or [`/docs/billing/llms-full.txt`](/docs/billing/llms-full.txt). The section's slug is the part of its pages' addresses that follows `/docs/`. [`/docs/api/llms-full.txt`](/docs/api/llms-full.txt) : The API guides and the whole generated API reference: every Client API and Organization API operation with its parameters, request body and answers. `/docs/llms-full.txt` leaves the reference out, because it is larger than every guide together. `/index.md` : Any single page as Markdown. Add `index.md` to the page's address: [/docs/get-started/for-llms/index.md](/docs/get-started/for-llms/index.md) is this page. `/docs/search.json?q=` : The site's search, as JSON. `/docs/search.json?q=floating+ip` answers `{"query": ..., "results": [...]}` with up to 12 results, each with its `title`, `url`, `section`, `kind` and a `snippet`. ## Give the docs to an assistant *An agent that can browse* : Such as a coding agent or an assistant with web access. Tell it to read `https://www.coritan.com/docs/llms.txt` and follow the links it needs. *A chat assistant without browsing* : Download [`/docs/llms-full.txt`](/docs/llms-full.txt), or one section's file, and attach it to the conversation. *Your own tool or retrieval index* : Fetch `/docs/llms.txt`, then each page's `index.md`, and index those. The [sitemap](/docs/sitemap.xml) lists every page's address too. The files change whenever the docs do, so fetch them again rather than keeping an old copy. Every change to Coritan that a customer could notice updates these docs at the same time. ## What the Markdown holds Each page's Markdown starts with its title, its one-line summary and a `Source:` line with the page's address, then has the same steps, tables, examples and links as the page. Links to other pages are paths on www.coritan.com such as `/docs/billing/add-credit/`, with `index.md` added for the Markdown. An assistant is only as right as what it reads. When an answer matters, such as a price, a limit or a step that deletes data, open the page it cites and check. ## Related - [Make your first API request](/docs/get-started/first-steps-with-the-api/) - [API](/docs/api/) # Account and security > Manage your profile, password, two-factor authentication, sessions and API keys from Settings in the dashboard. Source: https://www.coritan.com/docs/account/ In the dashboard: - /dashboard/settings: https://www.coritan.com/dashboard/settings Your Coritan account holds your services, your credit and your invoices. You sign in to it with an email address and a password, and you keep its details and its security in **Settings**. To open it, select **Settings** in the sidebar, or select your initials at the top right and then **Settings**. **Settings** has three tabs: **Profile** : Your name and company, the currency you pay in and your billing country. **Security** : Two-factor authentication, your password and the session in this browser. **API keys** : Keys you can create and revoke. The API does not accept them yet. ## Sign in and out - [Sign in to your account](/docs/account/sign-in/), with a code from your authenticator app if you use one. - [Change or reset your password](/docs/account/password/). - [Sign out and end sessions](/docs/account/sessions/). ## Keep your details up to date - [Update your profile](/docs/account/profile/): your name, company, currency and country. - [Currencies and countries](/docs/billing/currencies-and-regions/) lists what you can choose. ## Protect the account - [Turn on two-factor authentication](/docs/account/two-factor-authentication/). - [Manage API keys](/docs/account/api-keys/). - [Link your account to a Coritan staff account](/docs/account/staff-link/), if you work at Coritan. ## Billing An account costs nothing on its own. You pay for the services you order, from your credit balance or a saved payment method, as [Billing](/docs/billing/) explains. ## Limits - You cannot change your email address or your billing mode in the dashboard. [Contact support](/docs/support/) to change them, or to close the account. - The dashboard can only show the session in the browser you are using. [Sign out and end sessions](/docs/account/sessions/) explains how to end the others. - Two-factor authentication is optional, and it uses an authenticator app. Coritan sends no codes by text message or email. - The API accepts the access token that signing in returns. [Authentication](/docs/api/authentication/) has the details. # Sign in to your account > Sign in with your email address and password, finish with your second factor if you use one, and fix common sign-in problems. Source: https://www.coritan.com/docs/account/sign-in/ In the dashboard: - /login: https://www.coritan.com/login Sign in to reach the dashboard, your services and your billing. You sign in with the email address and password you chose when you [created the account](/docs/get-started/create-an-account/), and with a code from your phone if you use [two-factor authentication](/docs/account/two-factor-authentication/). ## Before you begin - A Coritan account and its password. If you have forgotten the password, [reset it](/docs/account/password/#reset-a-forgotten-password) first. - If two-factor authentication is on, your authenticator app, or one of your recovery codes. ## Sign in 1. Go to [coritan.com/login](https://www.coritan.com/login), or select **Sign in** at the top of any coritan.com page. 2. Enter your **Email address** and your **Password**. 3. If a verification check appears under the password, complete it. 4. Select **Sign in**. 5. If two-factor authentication is on, the page asks you to **Check your authenticator**. Type the six digits your app shows into **Authentication code**. The page signs you in as soon as you type the sixth digit. For a pasted code, select **Verify and sign in**. To use a recovery code instead, select **Use a recovery code instead** and type one of the codes you saved. Each recovery code works once. ## Result The dashboard opens. If you were on another dashboard page when you were asked to sign in, that page opens instead. After a recovery code, a message tells you how many codes you have left: [make a new set](/docs/account/two-factor-authentication/#make-new-recovery-codes) before they run out. The browser stays signed in until you sign out. [Sign out and end sessions](/docs/account/sessions/) explains how long that lasts and how to end it. ## Troubleshooting `Invalid email or password. Check both and try again.` : The email address or the password is wrong. The message is the same for both. Check them, or select **Forgot password?** to [reset the password](/docs/account/password/#reset-a-forgotten-password). `Account is suspended or closed` : The account cannot sign in. [Contact support](/docs/support/). `Complete the verification check to continue.` : The verification check under the password is not finished. Complete it, then select **Sign in** again. `The verification check did not pass. Complete it and try again.` : The check expired or failed. It resets on its own; complete it again. `Too many authentication attempts. Please try again later.` : Too many sign-in attempts failed from your network in the last minute. Wait a minute, then try again. `That code is not right` : The code is wrong or already used: each code works once. Wait for the next code in the app. If every code fails, check that your phone's clock sets itself automatically, because the codes depend on the time. `Invalid or expired token` : More than ten minutes passed between your password and your code. Select **Sign in to a different account** and sign in again. `Too many requests for this action. Please wait and try again.` : You tried more than ten codes in five minutes. Wait a few minutes before the next try. You lost your phone and your recovery codes : [Contact support](/docs/support/) and say which account it is. Coritan staff can turn two-factor authentication off on the account. You then sign in with your password and set it up again on your new phone. ## Related - [Change or reset your password](/docs/account/password/) - [Turn on two-factor authentication](/docs/account/two-factor-authentication/) - [Sign out and end sessions](/docs/account/sessions/) ## With the API Sign in with [`POST /auth/login`](/docs/api/reference/client/authentication/#op-post-api-v1-auth-login), then send the `access_token` it returns as `Authorization: Bearer `. [Make your first API request](/docs/get-started/first-steps-with-the-api/) walks through it with curl. ```bash curl -X POST https://api.coritan.com/api/v1/auth/login \ -H "Content-Type: application/json" \ -d '{"email": "alex@example.com", "password": "your password"}' ``` ```json {"access_token": "eyJhbGciOiJIUzI1NiIs...", "refresh_token": "eyJhbGciOiJIUzI1NiIs...", "token_type": "bearer", "expires_in": 1800} ``` `expires_in` is the access token's lifetime in seconds. When it runs out, exchange the `refresh_token` for a new pair with [`POST /auth/refresh`](/docs/api/reference/client/authentication/#op-post-api-v1-auth-refresh) and the body `{"refresh_token": "..."}`. The answer has the same fields, with a new refresh token to keep. When two-factor authentication is on, the sign-in answers with a pending token instead: ```json {"mfa_required": true, "mfa_setup_required": false, "mfa_token": "eyJhbGciOiJIUzI1NiIs...", "expires_in": 600, "token_type": "bearer"} ``` Send the code to [`POST /auth/mfa/verify`](/docs/api/reference/client/authentication-mfa/#op-post-api-v1-auth-mfa-verify) within ten minutes, with the `mfa_token` as the bearer token and `{"code": "123456"}` (or a recovery code) as the body. It answers with `access_token`, `refresh_token`, `token_type` and `expires_in`, plus `how` (`totp` or `recovery`) and `recovery_codes_left`. Every other endpoint refuses the `mfa_token` with `401` and `"error": "mfa_required"`. [`GET /auth/turnstile`](/docs/api/reference/client/authentication/#op-get-api-v1-auth-turnstile) says whether sign-in needs the verification check: `{"enabled": true, "site_key": "..."}`. When it is on, `POST /auth/login` also needs the `turnstile_token` the check produces, and answers `403` with `"error": "turnstile_failed"` without it. | Status | `detail` | Meaning | | --- | --- | --- | | `401` | `Invalid email or password` | The email address or the password is wrong. | | `403` | `Account is suspended or closed` | The account cannot sign in. | | `403` | `{"error": "turnstile_failed", ...}` | The verification check is on and the token is missing or failed. | | `401` | `That code is not right` | The second-factor code is wrong or already used. | | `401` | `Invalid refresh token` | `POST /auth/refresh` was sent an access token. | | `401` | `Token invalidated by password change` | The password changed after the token was issued. Sign in again. | | `429` | `Too many authentication attempts. Please try again later.` | Too many failed sign-ins or refreshes from one address in a minute. `Retry-After` says when to try again. | ## API - `POST /api/v1/auth/login`: Login (https://www.coritan.com/docs/api/reference/client/authentication/#op-post-api-v1-auth-login) - `POST /api/v1/auth/mfa/verify`: Second step of signing in (https://www.coritan.com/docs/api/reference/client/authentication-mfa/#op-post-api-v1-auth-mfa-verify) - `POST /api/v1/auth/refresh`: Refresh (https://www.coritan.com/docs/api/reference/client/authentication/#op-post-api-v1-auth-refresh) - `GET /api/v1/auth/turnstile`: Turnstile config (https://www.coritan.com/docs/api/reference/client/authentication/#op-get-api-v1-auth-turnstile) # Update your profile > Change your name and company on the Profile tab of Settings, or read and update your account through the API. Source: https://www.coritan.com/docs/account/profile/ In the dashboard: - /dashboard/settings/profile: https://www.coritan.com/dashboard/settings/profile Your profile holds your name and your company, beside the email address you sign in with. Keep it current on the **Profile** tab of **Settings**, where you also choose the currency you pay in and your billing country. ## Before you begin - Sign in to the [dashboard](https://www.coritan.com/dashboard). ## Change your name or company 1. In the sidebar, select **Settings**. The **Profile** tab opens. 2. In **Your details**, change **First name**, **Last name** or **Company**. Both names are required. **Company** is optional; empty it to remove it. 3. Select **Save changes**. ## Change your currency or country 1. On the **Profile** tab, find **Billing details**. 2. Choose a **Currency**: the currency Coritan charges your card or other payment method in. Prices, invoices and your credit stay in US dollars, and a payment in another currency is converted at the current rate. 3. Choose a **Country**. It decides which ways to pay you are offered. 4. Select **Save billing details**. [Currencies and countries](/docs/billing/currencies-and-regions/) explains both choices. ## Result A message confirms `Profile saved.` or `Billing details saved.`, and the new name shows in the dashboard straight away. The **Account** card beside the form shows what you cannot change here: your **Email**, the account **Status**, your **Billing mode**, your **Credit balance** and the date you joined (**Member since**). To change your email address or billing mode, [contact support](/docs/support/). ## Troubleshooting `Enter your first name. We print it on your invoices.` : The first name is empty. The same message appears for an empty last name. **Save changes** is greyed out : Nothing in the form has changed yet. **Could not save your profile** : The change did not save. The message under it gives the reason. Try again, and [contact support](/docs/support/) if it keeps failing. **Could not load your billing details** : The **Billing details** card did not load. Select **Try again**. ## Related - [Currencies and countries](/docs/billing/currencies-and-regions/) - [Billing](/docs/billing/) - [Change or reset your password](/docs/account/password/) ## With the API Read your account with [`GET /auth/me`](/docs/api/reference/client/authentication/#op-get-api-v1-auth-me): ```bash curl https://api.coritan.com/api/v1/auth/me \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "id": 4821, "email": "alex@example.com", "first_name": "Alex", "last_name": "Morgan", "company": null, "status": "active", "billing_mode": "prepaid", "credit_balance": 25.0, "currency": "USD", "country_code": "GB", "currency_source": "default", "created_at": "2026-09-01T10:15:00" } ``` `id` : Your account number. `email` : The address you sign in with. `first_name`, `last_name` and `company` : Your name and company. Each can be `null`. `status` : `active` for an account that can use the API. A suspended or closed account cannot sign in, so it never sees this answer. `billing_mode` : `prepaid` or `postpaid`. [Billing](/docs/billing/) explains the difference. `credit_balance` : Your credit, in US dollars. `currency` : The currency your payments are charged in, as a three-letter code. `country_code` : Your billing country, as a two-letter code, or `null`. `currency_source` : How the currency was set: `default`, `geo` (from your location), `user` (you chose it) or `admin` (Coritan set it). `created_at` : When you created the account. Change your names with [`PUT /auth/me`](/docs/api/reference/client/authentication/#op-put-api-v1-auth-me). Send only the fields you want to change; a field you leave out keeps its value, and an empty string clears it. The answer is the whole account, as above. ```bash curl -X PUT https://api.coritan.com/api/v1/auth/me \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"company": "Example Ltd"}' ``` The currency and country are saved with `PATCH /payments/preference`, as [Currencies and countries](/docs/billing/currencies-and-regions/#with-the-api) shows. The API has no endpoint to change your email address. ## API - `GET /api/v1/auth/me`: Get me (https://www.coritan.com/docs/api/reference/client/authentication/#op-get-api-v1-auth-me) - `PUT /api/v1/auth/me`: Update me (https://www.coritan.com/docs/api/reference/client/authentication/#op-put-api-v1-auth-me) # Change or reset your password > Change your password from Settings, or reset it from the sign-in page with an emailed link when you have forgotten it. Source: https://www.coritan.com/docs/account/password/ In the dashboard: - /reset-password: https://www.coritan.com/reset-password - /update-password: https://www.coritan.com/update-password Change your password on the **Security** tab of **Settings** when you know the current one. When you have forgotten it, reset it with a link that Coritan emails to you. Either way, every browser and script signed in to your account has to sign in again afterwards. ## Before you begin - To change the password: sign in to the [dashboard](https://www.coritan.com/dashboard) and have your current password to hand. - To reset it: have access to the inbox of the email address you sign in with. If you signed up with a Coritan mailbox, the link also goes to the recovery email you gave at sign-up. ## Password rules A new password must have: - 8–128 characters - a lowercase letter and an uppercase letter - a digit - a character that is neither a letter nor a digit, such as `!` or a space ## Change your password 1. In the sidebar, select **Settings**, then the **Security** tab. 2. In **Change password**, enter your **Current password**. 3. Enter the new password in **New password** and again in **Confirm new password**. 4. Select **Change password**. ## Reset a forgotten password 1. On the [sign-in page](https://www.coritan.com/login), select **Forgot password?**. 2. Enter your **Email address**. For a mailbox account, you can enter the recovery email instead. 3. If a verification check appears, complete it. 4. Select **Send reset link**. 5. Open the email from Coritan and select its link. The link works once, for one hour, and only the newest link you asked for works. 6. On **Set a new password**, enter the password in **New password** and again in **Confirm new password**. 7. Select **Update password**, then **Sign in** with the new password. ## Result Coritan emails you to say the password changed. Every session on the account ends, the one in this browser included, so the dashboard asks you to sign in again with the new password. If you signed up with a Coritan mailbox, its password in mail apps changes to the new one too. ## Troubleshooting `Incorrect current password` : The **Current password** is wrong. If you have forgotten it, select **Reset it by email** under the form. `The new password is the same as the current one.` : Choose a password you do not use already. `Password must contain at least one uppercase letter` : The new password breaks one of the [rules](#password-rules). The message names the rule, such as a missing digit or special character. `The two new passwords do not match.` : **Confirm new password** differs from **New password**. Type both again. The reset email does not arrive : Look in your spam folder. The page answers the same way whether or not an account uses the address, so check that you typed the address you sign in with. A suspended or closed account gets no email. `Too many requests for this action. Please wait and try again.` : You asked for several links in a short time. Wait a quarter of an hour, then ask again. `This reset link is invalid or has expired. Request a new one below.` : The link is older than an hour, was used already, or a newer link replaced it. Select **Request a new link**. `This link is missing its reset token. Open the link from the email in full, or request a new one.` : The address in your browser was cut short. Open the link from the email again, or copy all of it. ## Related - [Sign in to your account](/docs/account/sign-in/) - [Sign out and end sessions](/docs/account/sessions/) - [Turn on two-factor authentication](/docs/account/two-factor-authentication/) ## With the API Change the password with [`POST /auth/me/password`](/docs/api/reference/client/authentication/#op-post-api-v1-auth-me-password): ```bash curl -X POST https://api.coritan.com/api/v1/auth/me/password \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"current_password": "old password", "new_password": "new password"}' ``` ```json {"message": "Password changed successfully", "mailbox_synced": false} ``` `mailbox_synced` is `true` when your Coritan mailbox took the new password too. From then on, every token issued before the change answers `401` with `Token invalidated by password change`, the token that made the request included. Sign in again for new tokens. A wrong current password answers `401` `Incorrect current password`, and a password that breaks a rule answers `422` with the rule, such as `Password must contain at least one digit`. Ask for a reset link with [`POST /auth/forgot-password`](/docs/api/reference/client/authentication/#op-post-api-v1-auth-forgot-password) and `{"email": "alex@example.com"}`. It needs no token, and it always answers `{"ok": true, "message": "If an account exists for that address, a reset link is on its way"}`. When the [verification check](/docs/account/sign-in/#with-the-api) is on, add its `turnstile_token`. Asking too often answers `429` with `"error": "rate_limited"`. The link in the email is `https://www.coritan.com/update-password?token=...`. Send its `token` with the new password to [`POST /auth/reset-password`](/docs/api/reference/client/authentication/#op-post-api-v1-auth-reset-password): ```bash curl -X POST https://api.coritan.com/api/v1/auth/reset-password \ -H "Content-Type: application/json" \ -d '{"token": "token from the link", "new_password": "new password"}' ``` It answers `{"ok": true, "message": "Password updated"}`. An expired link answers `400` `This reset link has expired. Request a new one.`, and a used or unknown one `400` `Invalid reset token`. A suspended or closed account answers `403` `Account is suspended or closed`. ## API - `POST /api/v1/auth/me/password`: Change password (https://www.coritan.com/docs/api/reference/client/authentication/#op-post-api-v1-auth-me-password) - `POST /api/v1/auth/forgot-password`: Issue a reset link by email (https://www.coritan.com/docs/api/reference/client/authentication/#op-post-api-v1-auth-forgot-password) - `POST /api/v1/auth/reset-password`: Reset password (https://www.coritan.com/docs/api/reference/client/authentication/#op-post-api-v1-auth-reset-password) # Turn on two-factor authentication > Protect your account with an authenticator app, save your recovery codes, and turn two-factor authentication off again. Source: https://www.coritan.com/docs/account/two-factor-authentication/ In the dashboard: - /dashboard/settings/security: https://www.coritan.com/dashboard/settings/security With two-factor authentication on, signing in takes your password and a six-digit code from an authenticator app on your phone. Someone who learns your password still cannot sign in without the phone. Coritan sends no codes by text message or email: the app makes them. ## Before you begin - Sign in to the [dashboard](https://www.coritan.com/dashboard). - Install an authenticator app that makes time-based codes, such as 1Password, Google Authenticator, Microsoft Authenticator, Authy or Aegis. - Have somewhere safe to keep ten recovery codes, such as a password manager. ## Turn it on 1. In the sidebar, select **Settings**, then the **Security** tab. 2. In **Two-factor authentication**, select **Set up two-factor authentication**. 3. Scan the QR code with your authenticator app. The app adds `Coritan` and starts showing six digits that change every 30 seconds. If the app cannot scan, select **Cannot scan it? Enter the key manually** and type the key into the app, or select **Copy setup key**. The codes are time-based, six digits long, use SHA-1 and change every 30 seconds. 4. Type the six digits the app shows into **Confirm with the code from the app**, then select **Turn on two-factor**. 5. Save the ten recovery codes that appear: select **Copy codes** or **Download .txt**. 6. Tick **I have saved these codes somewhere safe.** and select **Done**. > [!IMPORTANT] > The recovery codes appear once. Each one signs you in one time if you lose your phone. Without the phone and without a code, only Coritan support can let you back in. ## Make new recovery codes When you have used some recovery codes, or think someone has seen them, make a new set. The card shows **Low** when you have two or fewer left. 1. On the **Security** tab, select **New recovery codes…**. 2. Type the code your app shows. You can select **Use a recovery code instead** and type one of your remaining codes. 3. Select **Make new codes**. 4. Save the ten new codes, tick **I have saved these codes somewhere safe.** and select **Done**. Your old codes stop working as soon as the new ones exist. ## Turn it off 1. On the **Security** tab, find **Danger zone** and select **Turn off two-factor…**. 2. Enter **Your password** and the code your app shows, or select **Use a recovery code instead**. 3. Select **Turn off two-factor**. Signing in goes back to your password alone, and Coritan deletes your recovery codes. You can turn it on again at any time. ## Result While it is on, the **Two-factor authentication** card shows **On**, the **Method** (**Authenticator app**), the date it was **Turned on** and how many **Recovery codes** you have left. Every sign-in now asks for a code after the password, and Coritan emails you when you turn it on or off. Browsers and scripts that are already signed in stay signed in. To make each of them sign in again with a code, [change your password](/docs/account/password/). ## Troubleshooting `That code is not right` : The code is wrong, or it was used already. Wait for the app to show the next code and type that. If every code fails, set your phone's clock to set itself automatically: the codes depend on the time. `The password is not right` : The password in **Turn off two-factor authentication** is wrong. `Too many requests for this action. Please wait and try again.` : You tried more than ten codes in five minutes. Wait a few minutes. **Could not start setting up two-factor** : The QR code did not load. Select **Try again**. `Two-factor is already on; turn it off before setting up again` : Two-factor authentication is on already, perhaps from another tab. Reload the page. To move it to a new phone, turn it off and set it up again. You lost your phone : Sign in with a recovery code, turn two-factor authentication off, then set it up on the new phone. Without a recovery code, [contact support](/docs/support/). ## Related - [Sign in to your account](/docs/account/sign-in/) - [Change or reset your password](/docs/account/password/) - [Link your account to a Coritan staff account](/docs/account/staff-link/), which needs two-factor authentication ## With the API Every call below takes your access token. Codes are six digits from the app, or a recovery code of the form `XXXX-XXXX-XX`. All the calls that check a code share one budget of ten tries in five minutes; the eleventh answers `429` with `"error": "rate_limited"`. Read the state with [`GET /auth/mfa`](/docs/api/reference/client/authentication-mfa/#op-get-api-v1-auth-mfa): ```json {"enabled": true, "enabled_at": "2026-09-16T10:02:11", "recovery_codes_left": 8, "session_pending": false} ``` `session_pending` is `true` when the request used the pending token from a sign-in that still needs its code. Turn it on in two calls: 1. [`POST /auth/mfa/setup`](/docs/api/reference/client/authentication-mfa/#op-post-api-v1-auth-mfa-setup), with no body, answers with a new `secret`, its `otpauth_uri`, a QR code as `qr_svg`, the `issuer` (`Coritan`) and the `account` (your email). Add the secret to your app. Calling it again before step 2 replaces the secret. ```bash curl -X POST https://api.coritan.com/api/v1/auth/mfa/setup \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` 2. [`POST /auth/mfa/enable`](/docs/api/reference/client/authentication-mfa/#op-post-api-v1-auth-mfa-enable) with the first code from the app: ```bash curl -X POST https://api.coritan.com/api/v1/auth/mfa/enable \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"code": "123456"}' ``` ```json {"enabled": true, "enabled_at": "2026-09-16T10:02:11", "recovery_codes": ["ABCD-EFGH-JK", "..."]} ``` `recovery_codes` holds the ten codes. The API never shows them again. Make new recovery codes with [`POST /auth/mfa/recovery-codes`](/docs/api/reference/client/authentication-mfa/#op-post-api-v1-auth-mfa-recovery-codes) and `{"code": "123456"}`. It answers `{"recovery_codes": [...]}`, and the old codes stop working. Turn it off with [`POST /auth/mfa/disable`](/docs/api/reference/client/authentication-mfa/#op-post-api-v1-auth-mfa-disable) and `{"password": "your password", "code": "123456"}`. It answers `{"enabled": false}`. [`POST /auth/mfa/verify`](/docs/api/reference/client/authentication-mfa/#op-post-api-v1-auth-mfa-verify) finishes a sign-in, as [Sign in to your account](/docs/account/sign-in/#with-the-api) shows. Called with a normal access token, it checks a code and answers `{"ok": true, "how": "totp", "recovery_codes_left": 8}`. A recovery code checked this way is used up. | Status | `detail` | Meaning | | --- | --- | --- | | `400` | `That code is not right` | The code is wrong or already used. `POST /auth/mfa/verify` answers `401` for this. | | `400` | `Start with setup before confirming a code` | `enable` came before `setup`. | | `409` | `Two-factor is already on; turn it off before setting up again` | `setup` on an account that has it on. | | `409` | `Two-factor is already on` | `enable` on an account that has it on. | | `400` | `Two-factor is not on` | `disable` or `recovery-codes` on an account that has it off. `verify` answers `Two-factor is not on for this account`. | | `400` | `The password is not right` | `disable` was sent the wrong password. | ## API - `GET /api/v1/auth/mfa`: User MFA status (https://www.coritan.com/docs/api/reference/client/authentication-mfa/#op-get-api-v1-auth-mfa) - `POST /api/v1/auth/mfa/setup`: User MFA setup (https://www.coritan.com/docs/api/reference/client/authentication-mfa/#op-post-api-v1-auth-mfa-setup) - `POST /api/v1/auth/mfa/enable`: User MFA enable (https://www.coritan.com/docs/api/reference/client/authentication-mfa/#op-post-api-v1-auth-mfa-enable) - `POST /api/v1/auth/mfa/verify`: Second step of signing in (https://www.coritan.com/docs/api/reference/client/authentication-mfa/#op-post-api-v1-auth-mfa-verify) - `POST /api/v1/auth/mfa/recovery-codes`: User MFA regenerate recovery codes (https://www.coritan.com/docs/api/reference/client/authentication-mfa/#op-post-api-v1-auth-mfa-recovery-codes) - `POST /api/v1/auth/mfa/disable`: User MFA disable (https://www.coritan.com/docs/api/reference/client/authentication-mfa/#op-post-api-v1-auth-mfa-disable) # Sign out and end sessions > Sign out of the dashboard, and learn how long a signed-in browser or script stays signed in and how to end every session. Source: https://www.coritan.com/docs/account/sessions/ A *session* is a browser or a script that is signed in to your account. Signing out ends the session in the browser you are using. To end every session at once, change your password. ## Before you begin - Sign in to the [dashboard](https://www.coritan.com/dashboard). ## Sign out of this browser 1. At the top right of the dashboard, select your initials. 2. Select **Sign out**. You can also sign out from **Settings**: on the **Security** tab, the **Sessions** card has **Sign out of this device**. On coritan.com's own pages, **Sign out** is at the top of the page. Signing out makes this browser forget its tokens. Coritan does not cancel them: a copy of a token taken from the browser keeps working until it expires. If you think someone has one, [end every session](#end-every-session). ## End every session 1. In the sidebar, select **Settings**, then the **Security** tab. 2. [Change your password](/docs/account/password/#change-your-password). Every token issued before the change stops working, the one in this browser included. Every browser and script then has to sign in again, with your new password and, if it is on, a code from your authenticator app. [Resetting a forgotten password](/docs/account/password/#reset-a-forgotten-password) has the same effect. ## How long a session lasts Signing in gives a browser or a script two tokens: Access token : Proves who you are on each request. It lasts a short time: 30 minutes unless Coritan changes the setting. Refresh token : Gets a new pair of tokens when the access token runs out. It lasts 30 days unless Coritan changes the setting, and each new pair starts a new refresh token with a full lifetime. The dashboard renews its tokens on its own, so a browser you use at least once within the refresh token's lifetime stays signed in until you sign out. A script does the same with `POST /auth/refresh`, as [Make your first API request](/docs/get-started/first-steps-with-the-api/#5-renew-the-access-token) shows. ## Result The browser you signed out of shows the sign-in page. After a password change, every other browser shows the sign-in page the next time it contacts Coritan, and scripts get `401` answers until they sign in again. The **Sessions** card on the **Security** tab lists only the browser you are using, marked **This device**. Coritan keeps no list of your other sessions, so the dashboard cannot show them or end them one at a time. ## Troubleshooting The dashboard signed you out on its own : The refresh token expired, someone changed or reset the password, or Coritan suspended the account. If you did not change the password, [reset it](/docs/account/password/#reset-a-forgotten-password) and [turn on two-factor authentication](/docs/account/two-factor-authentication/). A script gets `401` with `Token invalidated by password change` : The password changed after the script signed in. Sign it in again with the new password. A script gets `403` with `Account is suspended or closed` : The account cannot be used. [Contact support](/docs/support/). ## Related - [Change or reset your password](/docs/account/password/) - [Turn on two-factor authentication](/docs/account/two-factor-authentication/) - [Authentication](/docs/api/authentication/) ## With the API The API has no sign-out endpoint and no list of sessions. To end a script's session, delete its tokens. To end every session, change the password with `POST /auth/me/password`, as [Change or reset your password](/docs/account/password/#with-the-api) shows: every earlier token then answers `401` with `Token invalidated by password change`. # Manage API keys > Create, list and revoke API keys on the API keys tab of Settings, and learn what the platform API accepts today. Source: https://www.coritan.com/docs/account/api-keys/ In the dashboard: - /dashboard/settings/api-keys: https://www.coritan.com/dashboard/settings/api-keys The **API keys** tab of **Settings** lets you create keys for scripts, each with a label, a set of permissions and an optional list of addresses it may be used from. You see each key once, when you create it. > [!IMPORTANT] > The Coritan API does not accept these keys yet. A request that sends one, as `Authorization: Bearer ct_...` or any other way, answers `401`. Until that changes, a script signs in and sends the access token it gets back, as [Make your first API request](/docs/get-started/first-steps-with-the-api/) shows. Because no request can use a key, Coritan does not check its permissions or its allowed addresses either. Other Coritan credentials do work with their own APIs: - An [organization API key](/docs/organizations/api-keys/) calls the [Commerce API](/docs/organizations/storefront/commerce-api/) for one organization. - An [SMTP Relay API key](/docs/mail/smtp-relay/api-keys/) sends mail with the SMTP Relay API. ## Before you begin - Sign in to the [dashboard](https://www.coritan.com/dashboard). ## Create a key 1. In the sidebar, select **Settings**, then the **API keys** tab. 2. Select **Create API key…**. 3. Type a **Label**, such as `CI deploys`, so you can tell the key apart from your others. It can be up to 100 characters. 4. Under **Permissions**, tick **Read** or **Write** for each area the key needs: **Services**, **Container Apps**, **Cloud Compute**, **DNS**, **Floating IPs**, **Email**, **Storage**, **Websites and Edge Proxy**, **Billing** and **Support**. A new key starts with **Read-only**, which ticks every **Read** box. **Select all** ticks everything. 5. Optionally, list the addresses the key may be used from in **IP allow-list**: one IPv4 or IPv6 address or CIDR range per line, such as `203.0.113.10` or `2001:db8::/32`. Leave it empty to allow any address. 6. Select **Create API key**. 7. Copy the key from **Copy this key now**. It starts with `ct_`, and Coritan shows it only this once. Coritan emails you when a key is created. ## Revoke a key 1. On the **API keys** tab, select the bin icon at the end of the key's row. 2. Type the key's label to confirm. A key with no label asks for the word `revoke`. 3. Select **Revoke key**. > [!CAUTION] > Revoking deletes the key, and it cannot be undone. To replace a key, create a new one. ## Result The list shows each key with its label and creation date under **Key**, then **Permissions**, **Allowed from** (`Any address` when it has no allow-list) and **Last used**. **Last used** stays `Never`, because the API does not accept the keys yet. ## Troubleshooting `Enter a label so you can tell this key apart from your others.` : The **Label** is empty. Type one. `Line 2: enter an IP address or a CIDR range.` : A line in **IP allow-list** is not an address or a range. The message names the line. Fix it or delete it. `401` from the API with a `ct_` key : The API does not accept these keys yet. Sign in and use an access token instead, as [Make your first API request](/docs/get-started/first-steps-with-the-api/) shows. You lost a key : Coritan cannot show it again. Revoke it and create another. ## Related - [Make your first API request](/docs/get-started/first-steps-with-the-api/) - [Authentication](/docs/api/authentication/) - [Create organization API keys](/docs/organizations/api-keys/) ## With the API These endpoints take your access token. Create a key with [`POST /auth/me/api-keys`](/docs/api/reference/client/authentication/#op-post-api-v1-auth-me-api-keys). `label` is required (1–100 characters); `permissions` and `ip_whitelist` are optional lists of strings. The dashboard writes permissions as `area:read` or `area:write`, such as `services:read`. ```bash curl -X POST https://api.coritan.com/api/v1/auth/me/api-keys \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"label": "CI deploys", "permissions": ["services:read"], "ip_whitelist": ["203.0.113.10"]}' ``` ```json { "id": 17, "label": "CI deploys", "permissions": ["services:read"], "ip_whitelist": ["203.0.113.10"], "is_active": true, "last_used_at": null, "created_at": "2026-09-16T10:02:11", "raw_key": "ct_..." } ``` `raw_key` is in this answer only. List your keys, newest first, with [`GET /auth/me/api-keys`](/docs/api/reference/client/authentication/#op-get-api-v1-auth-me-api-keys). It takes `limit` (default 200, at most 500) and returns the same fields without `raw_key`. Revoke a key with [`DELETE /auth/me/api-keys/{key_id}`](/docs/api/reference/client/authentication/#op-delete-api-v1-auth-me-api-keys-key-id). It answers `{"message": "API key revoked"}`, or `404` `API key not found` for a key that is not yours or no longer exists. ## API - `GET /api/v1/auth/me/api-keys`: List keys (https://www.coritan.com/docs/api/reference/client/authentication/#op-get-api-v1-auth-me-api-keys) - `POST /api/v1/auth/me/api-keys`: Create key (https://www.coritan.com/docs/api/reference/client/authentication/#op-post-api-v1-auth-me-api-keys) - `DELETE /api/v1/auth/me/api-keys/{key_id}`: Revoke key (https://www.coritan.com/docs/api/reference/client/authentication/#op-delete-api-v1-auth-me-api-keys-key-id) # Link your account to a Coritan staff account > If you work at Coritan, link your own account to your staff account with a one-time link from the admin console, or remove the link. Source: https://www.coritan.com/docs/account/staff-link/ This page is for people who work at Coritan. Your staff account signs in to the admin console, and your own Coritan account signs in to coritan.com. Linking the two lets mailboxes that are shared with you as staff open in the webmail of your own account. ## Before you begin - A staff account that can sign in to the admin console. - Your own Coritan account, with [two-factor authentication](/docs/account/two-factor-authentication/) on. Staff mail opens in this account, so it needs the second factor. ## Link the accounts 1. In the admin console, open your own account page and make a one-time link for your Coritan account. The link works once, for ten minutes. Making a new one cancels the old one. 2. Open the link in a browser where you are signed in to your own Coritan account. It opens the **Security** tab of **Settings**, with a **Coritan staff** card. 3. Select **Link to my staff account**. ## Result A message confirms `Linked. Mailboxes shared with you as staff now open in your webmail.` The **Coritan staff** card names the staff account you linked, and those mailboxes appear among the mailboxes your [webmail](/docs/mail/webmail/mailboxes-and-sharing/) can open. If the staff account is later deactivated, the card says so, and staff mailboxes stop opening in your account. ## Remove the link 1. In the sidebar, select **Settings**, then the **Security** tab. 2. In the **Coritan staff** card, select **Unlink**. Staff mailboxes stop opening in your account. You can also remove the link from the admin console, and so can a colleague who manages staff accounts. ## Troubleshooting `Turn on two-factor authentication first: staff mail opens in this account.` : **Link to my staff account** stays greyed out until [two-factor authentication](/docs/account/two-factor-authentication/) is on. Turn it on, then open the link again. `That link has expired or was already used. Make a new one in the admin console.` : The link is more than ten minutes old, was used already, or a newer link replaced it. Make a new one. `That staff account is no longer active.` : The staff account was deactivated. Ask a colleague who manages staff accounts. `This account is already linked to another staff account.` : Remove the other link first, from this card or from the admin console. `Too many requests for this action. Please wait and try again.` : You tried more than ten links in an hour. Wait before the next try. ## Related - [Turn on two-factor authentication](/docs/account/two-factor-authentication/) - [Open and share mailboxes in webmail](/docs/mail/webmail/mailboxes-and-sharing/) ## With the API These endpoints take your access token. [`GET /auth/me/staff-link`](/docs/api/reference/client/authentication/#op-get-api-v1-auth-me-staff-link) answers with the linked staff account, or `{"linked": null}`: ```json {"linked": {"name": "Alex", "email": "alex@example.com", "active": true}} ``` [`POST /auth/me/staff-link`](/docs/api/reference/client/authentication/#op-post-api-v1-auth-me-staff-link) links the accounts. Send the `staff_link` value from the link as `code`: ```bash curl -X POST https://api.coritan.com/api/v1/auth/me/staff-link \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"code": "code from the link"}' ``` It answers with the same shape as `GET`. A refusal answers `422` with `{"detail": {"code": "...", "message": "..."}}`, where `code` is `invalid_code`, `inactive`, `mfa_required` or `already_linked` and `message` is the sentence shown above. More than ten tries in an hour answers `429`. [`DELETE /auth/me/staff-link`](/docs/api/reference/client/authentication/#op-delete-api-v1-auth-me-staff-link) removes the link and answers `{"linked": null}`. ## API - `GET /api/v1/auth/me/staff-link`: Get staff link (https://www.coritan.com/docs/api/reference/client/authentication/#op-get-api-v1-auth-me-staff-link) - `POST /api/v1/auth/me/staff-link`: Redeem staff link (https://www.coritan.com/docs/api/reference/client/authentication/#op-post-api-v1-auth-me-staff-link) - `DELETE /api/v1/auth/me/staff-link`: Remove staff link (https://www.coritan.com/docs/api/reference/client/authentication/#op-delete-api-v1-auth-me-staff-link) # Billing > How Coritan bills you, from your credit balance and invoices to payment methods, and where each lives on the Billing page. Source: https://www.coritan.com/docs/billing/ In the dashboard: - /dashboard/billing: https://www.coritan.com/dashboard/billing Coritan bills each service on the billing cycle you chose when you ordered it: by the hour, week, month, quarter or year. You pay from your *credit balance*, with a saved card or PayPal account, or with cryptocurrency. This section explains how charges are worked out and paid, and how to change or cancel what you pay for. ## How you are billed Orders : An order raises one first invoice for everything in it: one billing cycle of the service, the first period of each add-on bought with it and any setup fees. Coritan takes what it can from your credit, and sets everything in the order up once the invoice is paid ([Order a service](/docs/get-started/order-a-service/)). Renewals : Coritan raises each renewal invoice up to 3 days before the service's renewal date, by default. It takes the invoice from your credit first and charges your default payment method for the rest. Hourly services : After its first invoice, an hourly service has no invoices. Coritan takes its price from your credit every hour, up to a monthly cap ([How hourly billing works](/docs/billing/hourly-billing/)). Missed payments : Coritan retries a failed charge on a schedule. A service whose invoice stays unpaid is suspended a day after the due date, by default, and deleted later ([Failed payments and suspended services](/docs/billing/failed-payments/)). Your credit is held in US dollars and never expires. Invoices are in US dollars too. The currency your bank charges depends on how you pay ([Currencies and countries](/docs/billing/currencies-and-regions/)). ## The Billing page In the sidebar of the [dashboard](https://www.coritan.com/dashboard/billing), select **Billing**. The page has four tabs. **Overview** : Three cards across the top: **Account credit** (your balance, and whether automatic top-up is on), **Due now** (the total of your unpaid invoices) and **You pay with** (your default payment method). Under them are the **Add credit** card, **Waiting for payment** with your unpaid invoices, and **Recent activity** with your latest payments. The balance is in US dollars, even when your account's currency is another one and the card shows that currency's symbol. **Invoices** : Every invoice, with its status, and the **Pay** button for the unpaid ones ([Pay an invoice](/docs/billing/invoices/)). **Transactions** : Every payment, refund and credit movement ([Transactions and payment attempts](/docs/billing/transactions/)). **Payment methods** : Your saved cards and PayPal accounts, auto-pay and automatic top-up ([Manage payment methods](/docs/billing/payment-methods/)). ## Pay and add money - [Pay an invoice](/docs/billing/invoices/): pay what is due with credit, a saved method, a card, PayPal or another provider. - [Add credit to your balance](/docs/billing/add-credit/): top up by card, PayPal or a checkout page. - [Turn on automatic top-up](/docs/billing/automatic-top-up/): charge your default method when your balance runs low. - [Pay with cryptocurrency](/docs/billing/crypto-payments/): send cryptocurrency to add credit. - [Manage payment methods](/docs/billing/payment-methods/): save, remove and choose the default card or PayPal account. - [Currencies and countries](/docs/billing/currencies-and-regions/): which currency you are charged in, and why. ## Change what you pay for - [Change a service's plan](/docs/billing/change-plan/): move a service to another billing cycle of the same product. - [Cancel a service](/docs/billing/cancel-a-service/): end a service at the end of its term or at once. - [How hourly billing works](/docs/billing/hourly-billing/): hourly prices, the monthly cap and the $10.00 deposit that unlocks them. ## Records and problems - [Transactions and payment attempts](/docs/billing/transactions/): what you paid, when and how. - [Failed payments and suspended services](/docs/billing/failed-payments/): why a payment failed, and how to restore a suspended service. ## Prepaid and postpaid accounts Your account's *billing mode* is shown as **Billing mode** on your profile ([Update your profile](/docs/account/profile/)). `prepaid` : The mode every new account starts in. You pay a service's first invoice before Coritan sets it up. Hourly billing and automatic top-up are for prepaid accounts only. `postpaid` : A service on a monthly or longer billing cycle is set up without a first payment, and you pay an invoice at the end of each billing cycle. Hourly, daily and weekly plans still need their first payment before setup. Only Coritan staff can move an account to postpaid, so [contact support](/docs/support/conversations/) to ask. ## Limits | What | Limit | | --- | --- | | One top-up | $1.00 to $10,000.00 | | Credit that unlocks hourly billing | $10.00 added in total, by default | | Monthly cap of an hourly service | 672 hours of its hourly price, by default | | Renewal invoices | Raised up to 3 days before the renewal date, by default | | Suspension of an unpaid service | 1 day after the invoice's due date, by default | ## With the API [`GET /billing/credit`](/docs/api/reference/client/billing/#op-get-api-v1-billing-credit) returns your credit balance and your last 20 credit movements, newest first: ```bash curl https://api.coritan.com/api/v1/billing/credit \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "balance": 37.01, "currency": "USD", "recent_transactions": [ { "id": 9031, "amount": -0.0087, "balance_after": 37.01, "description": "Hourly charge for service #311", "created_at": "2026-09-25T09:00:02" }, { "id": 9024, "amount": 25.0, "balance_after": 37.0187, "description": "Credit top-up $25.00 USD", "created_at": "2026-09-24T16:41:10" } ] } ``` `balance` is in US dollars. `currency` is your account's currency, which is not the currency of `balance`: an account set to euros sees `EUR` beside a dollar amount. Each entry in `recent_transactions` is one change to your balance: `amount` is positive for credit added and negative for credit spent, and `balance_after` is the balance it left. The list holds movements that the **Transactions** tab does not show, such as hourly charges and the credit from a downgrade. The [Client API reference](/docs/api/reference/client/billing/) lists every billing operation. [Make your first API request](/docs/get-started/first-steps-with-the-api/) explains how to get `$CORITAN_TOKEN`. ## API - `GET /api/v1/billing/credit`: Get credit (https://www.coritan.com/docs/api/reference/client/billing/#op-get-api-v1-billing-credit) # Add credit to your balance > Top up your credit balance with a card or another payment method, and see how Coritan spends that credit. Source: https://www.coritan.com/docs/billing/add-credit/ Credit is money you pay in ahead of time. Coritan holds it in US dollars, it never expires, and Coritan spends it before it charges a payment method. Add credit to pay for hourly services, to cover renewals, or to pay an invoice without a card. ## Before you begin - Sign in to the [dashboard](https://www.coritan.com/dashboard). - Decide how much to add. One top-up is between $1.00 and $10,000.00. - Have a way to pay: a saved method, a card, a PayPal account or cryptocurrency. ## Add credit 1. In the sidebar, select **Billing**. The **Add credit** card is on the **Overview** tab. The **Add credit** button on the dashboard's home page opens the same card. 2. Under **Amount**, choose `$10`, `$25`, `$50`, `$100` or `$250`. To enter another amount, choose **Other** and type it in US dollars. 3. Under **Pay with**, choose how to pay: - A saved card or PayPal account. Coritan charges it straight away. - **Card** (or **New card**) to enter a card. - **PayPal** to pay with a PayPal account. PayPal's buttons appear in the card. - **Crypto** to send cryptocurrency. [Pay with cryptocurrency](/docs/billing/crypto-payments/) walks through it. - Any other provider, which opens its own checkout page. 4. Select the button at the bottom of the card: - `Add $25.00` (with your amount) for a saved method or a new card. - `Continue to` and the provider's name for a checkout page. Pay there, and the provider brings you back to **Billing**. - For PayPal, select PayPal's button and approve the payment in the window that opens. You stay on the page. 5. For a new card, enter it in the **Pay by card** dialog and select **Pay now**. If your bank asks you to confirm a saved card, the dialog is called **Confirm with your bank**. ## Result A message confirms the top-up, for example `$25.00 added to your balance.` Coritan emails you a receipt. The new balance shows on the **Overview** tab and in the sidebar, and the top-up appears on the **Transactions** tab ([Transactions and payment attempts](/docs/billing/transactions/)). Once the credit you have added comes to $10.00 or more in total, [hourly billing](/docs/billing/hourly-billing/) unlocks for your account. Your bank charges the amount in your account's currency, converted from US dollars ([Currencies and countries](/docs/billing/currencies-and-regions/#what-each-payment-is-charged-in)). ## How Coritan spends credit New orders : When you order a service, Coritan takes as much of the first invoice as your credit covers. If credit covers all of it, the service starts setting up at once. Renewals : Coritan takes each renewal invoice from your credit first, and charges your saved method only for what credit does not cover. Hourly services : Coritan takes each hour's charge from your credit ([How hourly billing works](/docs/billing/hourly-billing/)). Invoices you pay yourself : Choose **Account credit** in the **Pay** dialog ([Pay an invoice](/docs/billing/invoices/)). To keep the balance from running out, set up [automatic top-up](/docs/billing/automatic-top-up/). ## Troubleshooting `Enter an amount between $1.00 and $10,000.00.` : The amount is outside what one top-up can be. Choose another amount. **Could not add the credit** : The payment did not start. The message under it gives the reason. Try again, or choose another way to pay. `The bank did not approve the payment.` : Your bank refused the payment, or the check it asked for was not completed. Nothing was added. Try again or pay another way. `Top-up cancelled. You paid nothing.` : You left the provider's checkout page before paying. `Back from checkout. Your balance updates once the payment clears.` : The provider has not confirmed the payment yet. Check your balance again later; do not pay twice. `Your bank still needs to confirm this payment.` : Your bank has not finished its check. Complete it in the card dialog, or wait for the bank and check your balance later. **Could not confirm the top-up** : You paid, but Coritan could not confirm the payment when you came back. Wait a few minutes and check your balance. If the credit does not arrive, [contact support](/docs/support/) with the date and amount. ## Related - [Turn on automatic top-up](/docs/billing/automatic-top-up/) - [Pay with cryptocurrency](/docs/billing/crypto-payments/) - [How hourly billing works](/docs/billing/hourly-billing/) - [Billing](/docs/billing/) ## With the API Every top-up `amount` is in US dollars, from `1` to `10000`; anything else answers `422`. The payment is charged in `currency` when you send it, and otherwise in your account's currency. ### Charge a saved method [`POST /billing/topup/manual`](/docs/api/reference/client/billing/#op-post-api-v1-billing-topup-manual) charges a saved method (its `id` from `GET /payments/methods`) and adds the credit when the charge succeeds: ```bash curl -X POST https://api.coritan.com/api/v1/billing/topup/manual \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"amount": "25.00", "payment_method_id": 812}' ``` ```json { "message": "Top-up successful", "new_balance": 37.01, "status": "succeeded", "base_amount": "25.00", "fx_rate": "1", "currency": "USD" } ``` When the bank wants the cardholder to confirm, the answer has `status` `requires_action` and a `client_secret` for Stripe.js in a browser; nothing is credited until the confirmed payment reaches `confirm-payment` below. A declined charge answers `402` with the bank's reason, and a method that is not yours answers `404` with `Payment method not found`. ### Pay by card in a browser 1. Create the payment with [`POST /billing/topup/pay-intent`](/docs/api/reference/client/billing/#op-post-api-v1-billing-topup-pay-intent). Send `amount`, and optionally `gateway_name`, `currency` and `save_method` (`true` keeps the card for later charges). The answer has `status` `requires_payment_method`, a `client_secret` for Stripe.js, `gateway_reference` (the payment's id), `charge_amount` in the charged currency's smallest unit, `base_amount` and `fx_rate`. 2. The cardholder confirms the card in the browser with Stripe.js. 3. Call [`POST /billing/topup/confirm-payment`](/docs/api/reference/client/billing/#op-post-api-v1-billing-topup-confirm-payment) with the query parameters `gateway_name` and `payment_intent_id`. You can add `amount` to check it: the call answers `400` with `amount does not match the completed payment` when it differs from what was paid by more than two cents. ```bash curl -X POST "https://api.coritan.com/api/v1/billing/topup/confirm-payment?gateway_name=stripe&payment_intent_id=pi_3Qexample&amount=25" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "status": "succeeded", "gateway_name": "stripe", "gateway_reference": "pi_3Qexample", "client_secret": null, "requires_action_url": null, "new_balance": 37.01, "credited_usd": 25.0 } ``` It answers `requires_action` while the bank still wants a check, and `402` with `Payment not completed` when the payment failed. Coritan credits each payment once, so repeating the call is safe. ### Pay with PayPal or a checkout page [`POST /billing/topup/checkout`](/docs/api/reference/client/billing/#op-post-api-v1-billing-topup-checkout) creates a checkout. Send `amount`, `gateway_name`, `return_url` and `cancel_url`. Both URLs must be absolute `http` or `https` URLs on a real host; placeholder hosts such as `example.com` answer `400`. ```bash curl -X POST https://api.coritan.com/api/v1/billing/topup/checkout \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"amount": "25.00", "gateway_name": "paypal", "return_url": "https://www.coritan.com/dashboard/billing", "cancel_url": "https://www.coritan.com/dashboard/billing"}' ``` ```json { "checkout_url": "https://www.paypal.com/checkoutnow?token=5O190127TN364715T", "session_id": "5O190127TN364715T", "token": null, "gateway": "paypal", "currency": "USD", "charge_amount": 2500, "base_amount": "25.00", "fx_rate": "1" } ``` Send the payer to `checkout_url`. For most providers, the credit arrives when the provider confirms the payment to Coritan. For PayPal, `session_id` is the PayPal order: once the payer approves it, call [`POST /billing/topup/paypal-capture`](/docs/api/reference/client/billing/#op-post-api-v1-billing-topup-paypal-capture) with the query parameters `order_id` and `gateway_name` (and optionally `amount`). It answers like `confirm-payment`, or `402` with the reason when PayPal refuses the capture. ## API - `POST /api/v1/billing/topup/checkout`: Hosted checkout to buy USD account credit without a saved payment method (https://www.coritan.com/docs/api/reference/client/billing/#op-post-api-v1-billing-topup-checkout) - `POST /api/v1/billing/topup/pay-intent`: Create an in-page Stripe Payment Element intent for a credit top-up (https://www.coritan.com/docs/api/reference/client/billing/#op-post-api-v1-billing-topup-pay-intent) - `POST /api/v1/billing/topup/confirm-payment`: Finalize a top-up PaymentIntent after SCA and credit the user balance (https://www.coritan.com/docs/api/reference/client/billing/#op-post-api-v1-billing-topup-confirm-payment) - `POST /api/v1/billing/topup/paypal-capture`: Capture an approved PayPal order and credit the platform user balance (https://www.coritan.com/docs/api/reference/client/billing/#op-post-api-v1-billing-topup-paypal-capture) - `POST /api/v1/billing/topup/manual`: Manually top up credit balance by charging a payment method (https://www.coritan.com/docs/api/reference/client/billing/#op-post-api-v1-billing-topup-manual) # Manage payment methods > Save a card or another payment method, choose which one is your default, and remove methods you no longer use. Source: https://www.coritan.com/docs/billing/payment-methods/ In the dashboard: - /dashboard/billing/methods: https://www.coritan.com/dashboard/billing/methods A saved card or PayPal account lets Coritan charge renewals and automatic top-ups without you. You can still pay each invoice yourself without one. Manage your methods on the **Payment methods** tab of **Billing**, which also holds the auto-pay setting and [automatic top-up](/docs/billing/automatic-top-up/). ## Before you begin - Sign in to the [dashboard](https://www.coritan.com/dashboard). - Have the card or PayPal account to hand. The ways to save a method depend on your billing country ([Currencies and countries](/docs/billing/currencies-and-regions/#your-billing-country)). ## Save a payment method 1. In the sidebar, select **Billing**, then the **Payment methods** tab. 2. Select **Add payment method**. 3. If the dialog lists more than one **Kind of payment method**, choose one. 4. Leave **Use this for renewals and top-ups** ticked to make the method your default. Clear it to save the method without making it the default. 5. Finish for the kind you chose: - For a card, select **Enter card details**, enter the card and select **Save card**. Your bank may ask you to confirm. - For PayPal, select the PayPal button, sign in to PayPal in the window that opens and approve future payments. You stay on the page. - For any other provider, select the button that starts with `Continue to`, complete the provider's page, and it brings you back to the **Payment methods** tab. Coritan does not charge you when you save a method. Your bank may show a small authorisation that drops off. ## Change your default method Your default method is the one marked `Default`. Coritan charges it for renewals and automatic top-ups. To change it, select **Make default** on another method. ## Remove a payment method 1. Select **Remove…** on the method. 2. Read the dialog and select **Remove payment method**. Coritan stops charging the method. If it was your default, make another method the default before your next renewal; otherwise Coritan charges the method you saved most recently. ## Turn auto-pay on or off The card at the top of the tab says whether auto-pay is on. Select **Turn off auto-pay** or **Turn on auto-pay** to change it. Auto-pay is on for new accounts. > [!IMPORTANT] > Turning auto-pay off does not stop automatic charges at the moment. Coritan still takes each renewal invoice from your credit first and then charges your saved methods. To pay every renewal yourself, remove your saved methods and keep your balance at zero. ## Result A message confirms each change, for example `Visa ending in 4242 saved.`, `Visa ending in 4242 is now your default.` or `Payment method removed.` Each saved method shows its name, its expiry date (such as `Expires 08/2029`) or email address, and the date you added it. Adding a method does two more things: - Any renewal invoice still waiting for payment moves to the new method for its next try. - Retries that stopped because your bank refused a card start again with the new method ([Failed payments and suspended services](/docs/billing/failed-payments/)). A card you pay with in the dashboard also appears in your saved methods. It becomes the default when you have no default yet. ## Troubleshooting **No way to pay is available right now** : Nothing is set up to save a payment method for your country. [Contact support](/docs/support/). **Could not start adding a payment method** : The provider did not start the setup. The message under it gives the reason. Try again, or choose another kind of method. `The bank did not approve the card.` : Your bank refused the check. Nothing was saved. Try another card. `You cancelled adding the payment method.` : You left the provider's page before finishing. Nothing was saved. `Back from the payment provider. If the method was approved it appears below shortly.` : The dashboard could not tell whether the provider approved the method. Reload the tab after a minute. **Could not confirm the payment method** : The provider approved the method but Coritan could not save it. Add it again. Two methods show `Default` : Each payment provider keeps its own default, so a card and a PayPal account can both be marked `Default`. Coritan then cannot choose between them for renewals and automatic top-ups. Remove the one you do not want charged and add it again with **Use this for renewals and top-ups** cleared. **Could not change auto-pay** : The setting did not save. Try again. ## Related - [Keep your credit topped up](/docs/billing/automatic-top-up/) - [Pay an invoice](/docs/billing/invoices/) - [Failed payments and suspended services](/docs/billing/failed-payments/) ## With the API ### List your methods [`GET /payments/methods`](/docs/api/reference/client/payments/#op-get-api-v1-payments-methods) lists your saved methods, the default first and then the newest: ```bash curl https://api.coritan.com/api/v1/payments/methods \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json [ { "id": 812, "gateway_name": "stripe", "gateway_config_id": 3, "currency": "USD", "method_type": "card", "display_label": "Visa ending in 4242", "brand": "visa", "last4": "4242", "expires_month": 8, "expires_year": 2029, "email": null, "is_default": true, "is_active": true, "created_at": "2026-09-01T10:20:00" } ] ``` `id` is what you send as `payment_method_id` when you [pay an invoice](/docs/billing/invoices/#pay-with-a-saved-method) or [add credit](/docs/billing/add-credit/#with-the-api). `gateway_name` is the provider account that holds the method, and `currency` is the currency automatic charges to it are made in. `method_type` is `card`, `paypal` or another kind, such as `bank_debit` or `wallet`. ### Save a method Saving a method takes two calls, with a browser step between them for the cardholder: 1. Start with [`POST /payments/methods/setup`](/docs/api/reference/client/payments/#op-post-api-v1-payments-methods-setup). Send `return_url` and `cancel_url`, where the provider sends the payer back. `gateway_name` picks the provider account; leave it out and Coritan picks one for `currency` (default `USD`) and your billing country. `country_code` overrides the billing country for that choice. 2. Finish the payer's step. For a card, the answer's `client_secret` is for Stripe.js `confirmSetup` in a browser. For PayPal, `session_id` is the setup token the payer approves. For other providers, send the payer to `redirect_url`. 3. Call [`POST /payments/methods/confirm`](/docs/api/reference/client/payments/#op-post-api-v1-payments-methods-confirm) with `gateway_name`, `session_id` (the SetupIntent id for a card, or the approved PayPal token) and `set_as_default`. Send `currency` to choose the currency automatic charges use; it defaults to `USD`. ```bash curl -X POST https://api.coritan.com/api/v1/payments/methods/setup \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"gateway_name": "stripe", "return_url": "https://www.coritan.com/dashboard/billing/methods", "cancel_url": "https://www.coritan.com/dashboard/billing/methods"}' ``` ```json { "redirect_url": null, "client_secret": "seti_1Qexample_secret_example", "session_id": "seti_1Qexample", "requires_redirect": false, "gateway_name": "stripe", "currency": "USD" } ``` The confirm call answers with the saved method, in the same shape as the list. Both calls answer `400` with the reason when the provider cannot save a method, for example `No gateway account available for EUR`. ### Change the default or remove a method [`PUT /payments/methods/{method_id}/default`](/docs/api/reference/client/payments/#op-put-api-v1-payments-methods-method-id-default) makes a method the default and answers `{"message": "Default payment method updated"}`. It clears `is_default` only on your other methods from the same provider, as the dashboard does. [`DELETE /payments/methods/{method_id}`](/docs/api/reference/client/payments/#op-delete-api-v1-payments-methods-method-id) removes a method from Coritan and from the provider, and answers `{"message": "Payment method removed"}`. Both answer `404` with `Payment method not found` for a method that is not yours. ### See which providers can take a payment [`GET /payments/payment-config`](/docs/api/reference/client/payments/#op-get-api-v1-payments-payment-config) lists the provider accounts that can take a payment in a currency, as the dashboard's payment dialogs use it. It takes `currency` (default: your account's currency) and `country` (default: your billing country), and answers with `currency`, `country` and `accounts`. Each account has its `name`, `adapter` (`stripe`, `paypal` or another provider), `display_name`, the `method_types` it offers, whether it can save a method (`supports_tokenization`), charge a saved one (`supports_merchant_charge`) or run a checkout page (`supports_customer_checkout`), and the public keys a browser needs in `public_config`. The account for your country comes first, marked `preferred_for_country`. [`GET /payments/gateways`](/docs/api/reference/client/payments/#op-get-api-v1-payments-gateways) with `currency` lists the accounts that can save a method in that currency, in the same shape. Without `currency` it lists every provider with what it supports. ### Change the auto-pay setting Send `auto_pay_enabled` to [`PATCH /payments/preference`](/docs/api/reference/client/payments/#op-patch-api-v1-payments-preference). [`GET /payments/preference`](/docs/api/reference/client/payments/#op-get-api-v1-payments-preference) returns it with your currency and country ([Currencies and countries](/docs/billing/currencies-and-regions/#read-or-change-your-currency-and-country)). The setting has the limit described above. ```bash curl -X PATCH https://api.coritan.com/api/v1/payments/preference \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"auto_pay_enabled": false}' ``` ## API - `GET /api/v1/payments/methods`: List payment methods (https://www.coritan.com/docs/api/reference/client/payments/#op-get-api-v1-payments-methods) - `POST /api/v1/payments/methods/setup`: Setup payment method (https://www.coritan.com/docs/api/reference/client/payments/#op-post-api-v1-payments-methods-setup) - `POST /api/v1/payments/methods/confirm`: Confirm payment method (https://www.coritan.com/docs/api/reference/client/payments/#op-post-api-v1-payments-methods-confirm) - `PUT /api/v1/payments/methods/{method_id}/default`: Set default payment method (https://www.coritan.com/docs/api/reference/client/payments/#op-put-api-v1-payments-methods-method-id-default) - `DELETE /api/v1/payments/methods/{method_id}`: Remove payment method (https://www.coritan.com/docs/api/reference/client/payments/#op-delete-api-v1-payments-methods-method-id) - `GET /api/v1/payments/payment-config`: Eligible gateway accounts + non-secret public config for embedded UIs (https://www.coritan.com/docs/api/reference/client/payments/#op-get-api-v1-payments-payment-config) - `GET /api/v1/payments/gateways`: List live gateway accounts, optionally filtered by pay currency (https://www.coritan.com/docs/api/reference/client/payments/#op-get-api-v1-payments-gateways) - `GET /api/v1/payments/preference`: Get payment preference (https://www.coritan.com/docs/api/reference/client/payments/#op-get-api-v1-payments-preference) - `PATCH /api/v1/payments/preference`: Update payment preference (https://www.coritan.com/docs/api/reference/client/payments/#op-patch-api-v1-payments-preference) # Pay an invoice > Find your unpaid invoices, pay one with credit, a saved method or a new one, and read an invoice's lines and status. Source: https://www.coritan.com/docs/billing/invoices/ In the dashboard: - /dashboard/billing/invoices: https://www.coritan.com/dashboard/billing/invoices Coritan raises an invoice for every order, every renewal and every plan upgrade. Invoices are in US dollars. Pay one to start a new service, to keep a service running past its renewal date, or to lift a suspension. ## Before you begin - Sign in to the [dashboard](https://www.coritan.com/dashboard). - To pay with credit, have credit on your balance. [Add credit to your balance](/docs/billing/add-credit/) explains how. - To pay with a saved card or PayPal account, save it first on the **Payment methods** tab ([Manage payment methods](/docs/billing/payment-methods/)). You can also enter a new card or use PayPal while you pay. ## Find an invoice 1. In the sidebar, select **Billing**, then the **Invoices** tab. 2. Choose a filter: - **Unpaid** lists every invoice you still owe on: unpaid and overdue ones, however old. - **Paid** lists the invoices you have paid. - **All** lists everything, newest first. 3. Select **View** beside an invoice to read it. The table shows each invoice's number, **Status**, **Total**, the date it was **Issued** and the date it is **Due**. Choose how many rows a page shows at the bottom of the table, and move between pages with **Previous** and **Next**. ## Pay an invoice 1. Select **Pay** beside the invoice. The same button is on the **Overview** tab of **Billing**, in **Waiting for payment**. The dialog opens with the **Amount due**: the invoice total less anything already paid. 2. Choose how to pay: - **Account credit** takes the amount from your balance. It is greyed out when your balance is empty. - A saved card or PayPal account pays with that method. Your default method is marked `Default`. - **Card** opens a card form. It reads **New card** when you already have a saved method. - **PayPal** shows PayPal's buttons in the dialog. Approve the payment in the PayPal window that opens; you stay on the page. - Any other provider opens that provider's checkout page, then brings you back to the **Invoices** tab. 3. Select the button at the bottom of the dialog. It says what happens next: - **Pay with credit** when your credit covers the whole amount. - **Apply credit** when it covers part. The rest stays due, and you pay it the same way. - `Pay $12.99` (with the amount due) for a saved method or a new card. - `Continue to` and the provider's name for a checkout page. 4. If a card form opens (**Pay by card**, or **Confirm with your bank** when your bank asks for a check), complete it and select **Pay now**. You enter card details with Coritan's card processor; Coritan does not store them. ## Result A message confirms the payment, for example `Invoice INV-20260925-48213 paid.` The invoice moves to the **Paid** filter and Coritan emails you a receipt. Paying in full also does what the invoice was for: - A new service starts setting up. - A renewal moves the service's next renewal date on by one billing period. - An upgrade applies the new plan ([Change a service's plan](/docs/billing/change-plan/)). - A service suspended over this invoice is resumed. When a payment is accepted but not yet settled, you see `Payment submitted. The invoice updates once your bank settles it.` The invoice stays unpaid until the payment clears. Every payment also appears on the **Transactions** tab ([Transactions and payment attempts](/docs/billing/transactions/)). ## Read an invoice Select **View** to open an invoice. It shows: - **Issued**, **Due** and **Paid** (the date and time it was paid, or `Not yet`). - **Notes**, when the invoice has any. - One line per charge, with the **Item**, **Qty**, **Unit price** and **Amount**. - The **Subtotal**, **Tax** and **Total**. When part of the invoice is paid, **Already paid** and **Left to pay** follow. An unpaid invoice has a pay button at the bottom of the dialog, beside **Close**. Each invoice has one of these statuses: | Status | Meaning | | --- | --- | | `unpaid` | Issued and waiting for payment. | | `overdue` | Its due date has passed and it is still unpaid. The services on it are suspended soon after; [Failed payments and suspended services](/docs/billing/failed-payments/) has the timeline. | | `paid` | Paid in full. | | `cancelled` | Voided. You owe nothing on it. Coritan cancels an invoice when you cancel its service, or when a newer upgrade replaces an unpaid one. | | `refunded` | Paid, then refunded. | | `draft` | Not issued yet. | ## Troubleshooting **No way to pay is available right now** : Nothing is set up to take a payment for this invoice. [Contact support](/docs/support/). **Could not load the ways to pay** : The dialog could not read the payment options. Select **Try again**. **Could not take the payment** : The payment did not start. The message under it gives the reason, for example `Payment method 812 not found or inactive` when the saved method has been removed. **Could not start the payment** : The card form or the provider's checkout page could not be created. The message under it gives the reason. Try again, or choose another way to pay. `The payment was declined.` : Your bank or PayPal refused the charge. The message may give the bank's reason instead. Pay another way, or see [Failed payments and suspended services](/docs/billing/failed-payments/). `The bank did not approve the payment.` : The check your bank asked for failed or was abandoned. Nothing was charged. Try again, or pay another way. `Payment cancelled; the invoice is still due.` : You left the provider's checkout page before paying. Nothing was charged. `Back from checkout. The invoice updates once the payment clears.` : You came back from a checkout page before the provider confirmed the payment. Check the invoice again later; do not pay twice. **Could not open PayPal** : PayPal did not load in the dialog. Select **Try again**. If the message says `PayPal is not available in this browser. Choose another way to pay.`, use a card or another method. **Could not open the card form** : The card form did not load. Select **Try again**. **Could not apply your credit** : The message gives the reason, for example `No credit balance available`. The **Pay** button is missing : Only unpaid and overdue invoices can be paid. The invoice is already paid, cancelled or refunded. ## Related - [Add credit to your balance](/docs/billing/add-credit/) - [Manage payment methods](/docs/billing/payment-methods/) - [Failed payments and suspended services](/docs/billing/failed-payments/) - [Currencies and countries](/docs/billing/currencies-and-regions/) ## With the API ### List your invoices [`GET /billing/invoices`](/docs/api/reference/client/billing/#op-get-api-v1-billing-invoices) lists your invoices, newest first. It takes: `status_filter` : `open` (unpaid or overdue, what the **Unpaid** filter shows), or one status: `draft`, `unpaid`, `paid`, `cancelled`, `refunded` or `overdue`. Leave it out for every invoice. `page` and `limit` : The page to return (from `1`) and how many invoices a page holds (default `50`, at most `200`). `with_total` : `true` wraps the page in an object with the total and the count per status. Without it, the answer is a bare array of invoices. ```bash curl "https://api.coritan.com/api/v1/billing/invoices?status_filter=open&with_total=true" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "items": [ { "id": 5011, "invoice_number": "INV-20260925-48213", "user_id": 4821, "subtotal": 12.99, "tax": 0.0, "total": 12.99, "amount_paid": 0.0, "currency": "USD", "status": "unpaid", "due_date": "2026-09-28T00:00:00", "paid_at": null, "notes": null, "items": [ { "id": 9120, "service_id": 311, "description": "Service renewal - survival-smp", "item_type": "recurring", "quantity": 1.0, "unit_price": 12.99, "total": 12.99 } ], "created_at": "2026-09-25T02:00:00" } ], "total": 1, "page": 1, "limit": 50, "counts": {"all": 14, "open": 1, "draft": 0, "unpaid": 1, "paid": 13, "cancelled": 0, "refunded": 0, "overdue": 0} } ``` `total` counts the invoices that match the filter. `counts` holds every status whatever the filter, with `all` and `open` added. In each invoice: `id` and `invoice_number` : The number the API uses in paths, and the number printed on the invoice. `subtotal`, `tax` and `total` : The amounts in US dollars. `amount_paid` : What credit and earlier payments have covered. What is left to pay is `total` minus `amount_paid`. `currency` : `USD`. `status` : One of the statuses in the table above. `due_date`, `paid_at` and `created_at` : When the invoice falls due, when it was paid (or `null`) and when it was issued. `items` : The lines. `service_id` is the service a line bills, and `item_type` is `recurring`, `one_time`, `proration`, `addon`, `discount` or `manual`. [`GET /billing/invoices/{invoice_id}`](/docs/api/reference/client/billing/#op-get-api-v1-billing-invoices-invoice-id) returns one invoice in the same shape. It answers `404` with `Invoice not found`, or `403` with `Access denied` for an invoice on another account. ### Pay with credit [`POST /billing/invoices/{invoice_id}/apply-credit`](/docs/api/reference/client/billing/#op-post-api-v1-billing-invoices-invoice-id-apply-credit) takes as much of the amount due as your balance covers: ```bash curl -X POST https://api.coritan.com/api/v1/billing/invoices/5011/apply-credit \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "invoice_id": 5011, "applied": "12.99", "fully_paid": true, "credit_balance": "12.01", "message": "Invoice paid with credit" } ``` When credit covers only part, `fully_paid` is `false` and `message` is `Partial credit applied`. When nothing can be applied, the answer is `400` with the reason, such as `No credit balance available`. ### Pay with a saved method [`POST /payments/invoices/{invoice_id}/charge`](/docs/api/reference/client/payments/#op-post-api-v1-payments-invoices-invoice-id-charge) charges a saved method. Find its `id` with `GET /payments/methods` ([Manage payment methods](/docs/billing/payment-methods/#with-the-api)). ```bash curl -X POST https://api.coritan.com/api/v1/payments/invoices/5011/charge \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: pay-inv-5011-1" \ -d '{"payment_method_id": 812}' ``` The body takes: `payment_method_id` : The saved method to charge. Required. `amount_usd` : Pay part of the invoice: this many US dollars, at most what is left. Leave it out to pay the rest in full. `currency` : The currency to charge the method in. Leave it out to use your account's currency ([Currencies and countries](/docs/billing/currencies-and-regions/)). Send an `Idempotency-Key` header so that retrying a request whose answer you did not receive does not charge twice ([Idempotency](/docs/api/idempotency/)). The answer is the payment attempt: ```json { "id": 7730, "invoice_id": 5011, "user_id": 4821, "user_email": "alex@example.com", "gateway_name": "stripe", "gateway_config_id": 3, "payment_method_id": 812, "amount": 1299, "currency": "USD", "base_amount": "12.99", "fx_rate": "1", "status": "succeeded", "gateway_reference": "pi_3Qexample", "error_message": null, "decline_type": null, "decline_code": null, "is_auto_charge": false, "charge_schedule_id": null, "idempotency_key": "pay-inv-5011-1", "refunded_amount": null, "created_at": "2026-09-25T09:12:40", "completed_at": "2026-09-25T09:12:42", "gateway_response": {"id": "pi_3Qexample", "status": "succeeded"}, "client_secret": null } ``` `amount` is in the charged currency's smallest unit (cents here). `base_amount` is the US dollar amount it pays and `fx_rate` the exchange rate used, both as decimal strings. `gateway_reference` is the provider's id for the payment, and `gateway_response` holds the provider's own answer. `status` is one of: `succeeded` : Paid. `processing` : Accepted but not settled. The invoice updates when it settles. `requires_action` : The bank wants the cardholder to confirm. `client_secret` lets a browser page finish it with Stripe.js; then call `confirm-payment` as below. A script alone cannot complete this step. `failed` : Declined. `error_message` gives the reason, and `decline_type` says whether retrying can work (`soft`) or not (`hard`). `POST /payments/invoices/{invoice_id}/pay` is the older path for the same charge, with the same body and answer. ### Pay with a new card, PayPal or a checkout page These flows need a browser for the cardholder's part; the API creates and finishes each payment. 1. List what can take this invoice with [`GET /payments/invoices/{invoice_id}/gateways`](/docs/api/reference/client/payments/#op-get-api-v1-payments-invoices-invoice-id-gateways). Each entry has a `name` to send as `gateway_name`, and `adapter` says what it is (`stripe` for cards, `paypal`, or another provider). 2. For a card, create a payment with [`POST /payments/invoices/{invoice_id}/pay-intent`](/docs/api/reference/client/payments/#op-post-api-v1-payments-invoices-invoice-id-pay-intent). The answer's `client_secret` is for Stripe.js in the browser. Asking again for the same invoice and amount returns the same payment. After the card is confirmed, call [`POST /payments/invoices/{invoice_id}/confirm-payment`](/docs/api/reference/client/payments/#op-post-api-v1-payments-invoices-invoice-id-confirm-payment) with the query parameters `gateway_name` and `payment_intent_id`. It answers `succeeded`, `pending` or `requires_action`, or `402` with `Payment not completed`. 3. For PayPal or another provider, call [`POST /payments/invoices/{invoice_id}/checkout`](/docs/api/reference/client/payments/#op-post-api-v1-payments-invoices-invoice-id-checkout) with `gateway_name`, `return_url` and `cancel_url`. Both must be absolute `http` or `https` URLs on a real host: the API answers `400` for a placeholder host such as `example.com` or `localhost`. Send the payer to `checkout_url`. For PayPal, `session_id` is the PayPal order; once the payer approves it, call [`POST /payments/invoices/{invoice_id}/paypal-capture`](/docs/api/reference/client/payments/#op-post-api-v1-payments-invoices-invoice-id-paypal-capture) with the query parameters `order_id` and `gateway_name`. ```bash curl -X POST https://api.coritan.com/api/v1/payments/invoices/5011/checkout \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"gateway_name": "paypal", "return_url": "https://www.coritan.com/dashboard/billing/invoices", "cancel_url": "https://www.coritan.com/dashboard/billing/invoices"}' ``` A checkout that is still open for the invoice is returned again instead of a new one, with `reused` set to `true`. The checkout answers `400` when the invoice is already paid or cancelled. ## API - `GET /api/v1/billing/invoices`: Newest first, ties by id so a page boundary never repeats or skips a row (https://www.coritan.com/docs/api/reference/client/billing/#op-get-api-v1-billing-invoices) - `GET /api/v1/billing/invoices/{invoice_id}`: Get invoice (https://www.coritan.com/docs/api/reference/client/billing/#op-get-api-v1-billing-invoices-invoice-id) - `POST /api/v1/billing/invoices/{invoice_id}/apply-credit`: Apply account credit to an unpaid invoice (unlocks provision / plan change when paid) (https://www.coritan.com/docs/api/reference/client/billing/#op-post-api-v1-billing-invoices-invoice-id-apply-credit) - `GET /api/v1/payments/invoices/{invoice_id}/gateways`: List invoice gateways (https://www.coritan.com/docs/api/reference/client/payments/#op-get-api-v1-payments-invoices-invoice-id-gateways) - `POST /api/v1/payments/invoices/{invoice_id}/charge`: Canonical saved-method charge for an invoice (alias of /pay) (https://www.coritan.com/docs/api/reference/client/payments/#op-post-api-v1-payments-invoices-invoice-id-charge) - `POST /api/v1/payments/invoices/{invoice_id}/pay`: Charge a saved payment method for an invoice (legacy path; prefer /charge) (https://www.coritan.com/docs/api/reference/client/payments/#op-post-api-v1-payments-invoices-invoice-id-pay) - `POST /api/v1/payments/invoices/{invoice_id}/checkout`: Create invoice checkout (https://www.coritan.com/docs/api/reference/client/payments/#op-post-api-v1-payments-invoices-invoice-id-checkout) - `POST /api/v1/payments/invoices/{invoice_id}/pay-intent`: Create an in-page Stripe Payment Element intent for an invoice (https://www.coritan.com/docs/api/reference/client/payments/#op-post-api-v1-payments-invoices-invoice-id-pay-intent) - `POST /api/v1/payments/invoices/{invoice_id}/confirm-payment`: Finalize a platform invoice charge after customer completes SCA (https://www.coritan.com/docs/api/reference/client/payments/#op-post-api-v1-payments-invoices-invoice-id-confirm-payment) - `POST /api/v1/payments/invoices/{invoice_id}/paypal-capture`: Capture an approved PayPal order for a platform invoice (https://www.coritan.com/docs/api/reference/client/payments/#op-post-api-v1-payments-invoices-invoice-id-paypal-capture) # Transactions and payment attempts > The transaction types on your account, the payment attempts behind them, and disputes, with every field the API returns. Source: https://www.coritan.com/docs/billing/transactions/ In the dashboard: - /dashboard/billing/transactions: https://www.coritan.com/dashboard/billing/transactions A *transaction* is a record of money moving on your account: a payment, a refund, or credit added or used. The **Transactions** tab lists them newest first. Behind each card or PayPal payment is a *payment attempt*, one try at charging the method, and a *dispute* is a payment you asked your bank to reverse. The API lists all three. ## The Transactions tab In the sidebar of the [dashboard](https://www.coritan.com/dashboard/billing/transactions), select **Billing**, then the **Transactions** tab. **What** : The transaction's description, such as `Payment for invoice INV-20260925-48213`. **See invoice** opens the invoice it belongs to. **Type** : `Payment`, `Refund`, `Credit added` or `Credit used`. **Paid with** : How the money moved: `Card`, `PayPal`, `Crypto` or `Account credit`. A payment from your credit balance shows `credit_balance`. **Amount** : In US dollars. Credit added has a `+` in front, and credit used a `−`. **When** : The date and time of the transaction. Choose **All**, **Payments**, **Refunds** or **Credit** to show one kind. **Credit** shows credit added and credit used together, and each choice shows how many transactions it holds. Under the list, **Show** sets how many rows a page holds (10, 25, 50 or 100), and **Previous** and **Next** move between pages. ## Transaction types | Type | Shown as | Recorded when | Description | | --- | --- | --- | --- | | `payment` | `Payment` | You pay an invoice with a card, PayPal or a checkout page, or Coritan charges your saved method for it. | `Payment for invoice INV-20260925-48213` | | `credit_deduct` | `Credit used` | Coritan pays an invoice from your credit balance, in full or in part. | `Credit applied to invoice INV-20260925-48213`, or `Credit applied to first payment INV-20260925-48213` for a new order or a plan change | | `credit_add` | `Credit added` | You add credit: with a saved method, a card, PayPal, a checkout page, automatic top-up or cryptocurrency. | `Manual top-up $25.00`, `Credit top-up $25.00 USD`, `Auto top-up $25.00` or `40.2 USDC deposit on Ethereum` | | `refund` | `Refund` | Coritan returns a payment to the card or account it came from. | `Refund for invoice INV-20260925-48213` | Two movements of your credit balance are not transactions, so the tab does not list them: the charges for [hourly services](/docs/billing/hourly-billing/), and the credit from a [plan change](/docs/billing/change-plan/) to a cheaper plan. `GET /billing/credit` lists them ([Billing](/docs/billing/#with-the-api)). ## Payment attempts Each time Coritan tries to charge a card or PayPal account, it records a payment attempt with its outcome. A declined renewal charge, for example, is a `failed` attempt with the bank's reason, and the successful retry that follows is a second attempt. The dashboard does not show attempts. The API returns your last 50. `status` : `pending` (started), `processing` (accepted and settling, which some payment types take days to do), `requires_action` (waiting for you to confirm with your bank), `succeeded`, `failed` or `cancelled`. `amount` and `currency` : What was charged, in the smallest unit of the charged currency: `2500` in `USD` is $25.00. `base_amount` and `fx_rate` : The amount in US dollars, and the rate used to convert it into `currency`. `error_message`, `decline_type` and `decline_code` : Why a charge failed. `decline_type` is `soft` for a decline worth retrying, such as insufficient funds, and `hard` for one that needs a new payment method, such as an expired card. [Failed payments and suspended services](/docs/billing/failed-payments/) explains what each means for your renewals. `invoice_id` and `payment_method_id` : The invoice the attempt paid, and the saved method it charged. Both are `null` when they do not apply, such as a top-up with a new card. `is_auto_charge` and `charge_schedule_id` : Whether Coritan made the charge itself, such as a renewal retry, and the retry schedule it belongs to. `gateway_name` and `gateway_reference` : The payment provider account that handled the charge, and the provider's own ID for it. `refunded_amount` : How much of the charge was refunded, in the same unit as `amount`. `idempotency_key` : The `Idempotency-Key` sent with the payment, if any ([Idempotent requests](/docs/api/idempotency/)). `created_at` and `completed_at` : When the attempt started and when it finished. Each attempt also carries `user_id`, `user_email`, `gateway_config_id`, `gateway_response` (the provider's answer) and `client_secret` (used to confirm a card payment in the browser). ## Disputes A dispute, or *chargeback*, is a payment you asked your bank or PayPal to reverse. The dashboard does not show disputes. The API lists the ones on your payments and invoices. > [!CAUTION] > When you dispute a payment to Coritan, Coritan deletes every service on your account and closes the account, by default. Winning the dispute later does not bring deleted services back. If a charge looks wrong, [contact support](/docs/support/conversations/) before you go to your bank. `status` and `reason` : The payment provider's own words for the state of the dispute, such as `needs_response`, and for its cause. `amount` and `currency` : The amount disputed, in the smallest unit of `currency`. `evidence_due_at` : The date by which Coritan must answer the provider. `payment_attempt_id`, `invoice_id` and `gateway_reference` : The payment attempt and invoice the dispute is about, and the provider's ID for the disputed payment. `gateway_name` and `gateway_dispute_id` : The provider that holds the dispute, and its ID for it. `created_at` and `updated_at` : When Coritan recorded the dispute and last updated it. ## With the API ### List transactions [`GET /billing/transactions`](/docs/api/reference/client/billing/#op-get-api-v1-billing-transactions) lists your transactions, newest first. It takes `page` (from 1), `limit` (default 50, at most 200) and `type_filter`: `payment`, `refund`, `credit_add`, `credit_deduct`, or `credit` for both credit types. Add `with_total=true` to get the page with its counts: ```bash curl "https://api.coritan.com/api/v1/billing/transactions?type_filter=payment&limit=25&with_total=true" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "items": [ { "id": 7310, "user_id": 1482, "invoice_id": 5120, "amount": 13.47, "currency": "USD", "transaction_type": "payment", "gateway": "stripe", "gateway_reference": "pi_3Qexample", "description": "Payment for invoice INV-20260925-48213", "created_at": "2026-09-25T10:12:44" } ], "total": 1, "page": 1, "limit": 25, "counts": {"all": 7, "credit": 6, "payment": 1, "refund": 0, "credit_add": 4, "credit_deduct": 2} } ``` `total` is how many transactions match `type_filter`, and `counts` holds the number of each type whatever the filter. Without `with_total`, the answer is the list of transactions alone. `amount` is in US dollars and is negative for a refund. `gateway` is `credit_balance` for credit used, and `null` or a provider name otherwise. A `type_filter` outside the list answers `422`. ### List payment attempts [`GET /payments/attempts`](/docs/api/reference/client/payments/#op-get-api-v1-payments-attempts) returns your last 50 payment attempts, newest first. It takes no parameters. ```bash curl https://api.coritan.com/api/v1/payments/attempts \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` Each item has the fields under [Payment attempts](#payment-attempts). ### List disputes [`GET /payments/disputes`](/docs/api/reference/client/payments/#op-get-api-v1-payments-disputes) lists your disputes, newest first. It takes `limit` (default 50, at most 100) and `offset`: ```bash curl "https://api.coritan.com/api/v1/payments/disputes?limit=20" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "disputes": [], "limit": 20, "offset": 0 } ``` Each item in `disputes` has the fields under [Disputes](#disputes) and its `id`. ## API - `GET /api/v1/billing/transactions`: As listinvoices: newest first, ties by id, withtotal for a TransactionPage (https://www.coritan.com/docs/api/reference/client/billing/#op-get-api-v1-billing-transactions) - `GET /api/v1/payments/attempts`: List payment attempts (https://www.coritan.com/docs/api/reference/client/payments/#op-get-api-v1-payments-attempts) - `GET /api/v1/payments/disputes`: List disputes tied to the caller's payment attempts / invoices only (https://www.coritan.com/docs/api/reference/client/payments/#op-get-api-v1-payments-disputes) # Turn on automatic top-up > Let Coritan charge your default payment method when your credit balance falls below a threshold you choose. Source: https://www.coritan.com/docs/billing/automatic-top-up/ Hourly services draw on your credit as they run, and Coritan suspends them when the credit runs out. Automatic top-up adds credit for you: when your balance falls below a threshold, Coritan charges your default payment method a fixed amount. New accounts start with it off. ## Before you begin - Sign in to the [dashboard](https://www.coritan.com/dashboard). - Save a payment method that Coritan can charge without you, such as a card or a PayPal account ([Manage payment methods](/docs/billing/payment-methods/)). Coritan charges your default method, or the method you saved most recently when none is the default. - Your account pays in advance. Automatic top-up does not run on an account that Coritan bills afterwards. ## Turn it on 1. In the sidebar, select **Billing**, then the **Payment methods** tab. 2. Turn on the **Keep credit topped up** switch at the top of its card. 3. In **When my balance drops below**, enter the balance that starts a top-up, in US dollars. 4. In **Add**, enter how much to add each time, in US dollars. It must be at least $1. 5. Select **Save changes**. To turn it off, turn off the switch and select **Save changes**. ## Result A message confirms the setting, for example `Auto top-up turned on: $50.00 whenever the balance drops below $25.00.` The **Overview** tab of **Billing** shows the same rule under your balance. Coritan checks balances every 15 minutes by default. When yours is below the threshold, it charges the amount to your default method, adds it to your balance and emails you a receipt. It tops up at most once an hour, so one top-up that leaves you under the threshold is not followed by another straight away. The top-up appears on the **Transactions** tab as `Auto top-up $50.00`. When an hourly service is about to be suspended for lack of credit, Coritan tries a top-up first ([How hourly billing works](/docs/billing/hourly-billing/)). The charge is made in the currency your payment method was saved in ([Currencies and countries](/docs/billing/currencies-and-regions/#what-each-payment-is-charged-in)). ## Troubleshooting `Enter $0 or more.` : The threshold is negative or not a number. `Enter at least $1.` : The amount to add is below $1 or not a number. **Could not save auto top-up** : The setting did not save. The message under it gives the reason. Try again. **Could not load your auto top-up settings** : The card did not load. Select **Try again**. A top-up did not happen : Coritan emails you when an automatic top-up fails, once until the next one succeeds. The usual causes are a declined card and no saved method. Check the email, update your method on the **Payment methods** tab, or [add credit](/docs/billing/add-credit/) yourself. ## Related - [Add credit to your balance](/docs/billing/add-credit/) - [How hourly billing works](/docs/billing/hourly-billing/) - [Manage payment methods](/docs/billing/payment-methods/) ## With the API Read the setting with [`GET /billing/topup/config`](/docs/api/reference/client/billing/#op-get-api-v1-billing-topup-config): ```bash curl https://api.coritan.com/api/v1/billing/topup/config \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "auto_topup_enabled": true, "auto_topup_threshold": "25.0000", "auto_topup_amount": "50.0000", "credit_balance": "37.0100", "total_deposited": "125.0000", "hourly_billing_unlocked": true } ``` The amounts are US dollars, as decimal strings. `total_deposited` is all the credit you have added, and `hourly_billing_unlocked` says whether you can order hourly services. Change it with [`PUT /billing/topup/config`](/docs/api/reference/client/billing/#op-put-api-v1-billing-topup-config). `enabled` is required; `threshold` (`0` or more) and `amount` (`1` or more) are optional and keep their values when you leave them out. ```bash curl -X PUT https://api.coritan.com/api/v1/billing/topup/config \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"enabled": true, "threshold": "25", "amount": "50"}' ``` ```json {"message": "Auto top-up configuration updated"} ``` A value out of range answers `422`. ## API - `GET /api/v1/billing/topup/config`: Get topup config (https://www.coritan.com/docs/api/reference/client/billing/#op-get-api-v1-billing-topup-config) - `PUT /api/v1/billing/topup/config`: Update topup config (https://www.coritan.com/docs/api/reference/client/billing/#op-put-api-v1-billing-topup-config) # Pay with cryptocurrency > Add credit by sending cryptocurrency to your own deposit address, and follow each deposit until it is credited. Source: https://www.coritan.com/docs/billing/crypto-payments/ You pay in cryptocurrency by adding it to your credit balance. Coritan gives you a deposit address of your own, tells you exactly how much to send for the credit you want, and credits your balance in US dollars once the transfer has enough confirmations. That credit then pays for orders, renewals and hourly charges like any other credit ([How Coritan spends credit](/docs/billing/add-credit/#how-coritan-spends-credit)). ## Before you begin - Sign in to the [dashboard](https://www.coritan.com/dashboard). - Have a wallet or exchange account that can send the coin on the network you choose. For XRP, it must be able to send a destination tag. - Decide how much credit you want. In the dashboard, one deposit is between $1.00 and $10,000.00. - Make sure Coritan has issued you an address for the network. The dashboard shows an address only once Coritan has issued it, and it does not issue the first one. If you have never had an address for the network, issue it with the API ([Get your deposit address](#get-your-deposit-address)). One address serves six networks ([Networks and addresses](#networks-and-addresses)). ## Send a deposit 1. In the sidebar, select **Billing**. The **Add credit** card is on the **Overview** tab. 2. Under **Amount**, choose the credit you want, or choose **Other** and type an amount in US dollars. 3. Under **Pay with**, choose **Crypto**. 4. Under **Coin**, choose the coin and the network you will send it on, for example `USDC on Base`. The minimum for that coin shows under the list, for example `Minimum $0.50 for USDC.` 5. Select **Show where to send**. The card shows `Send USDC on Base`, the amount under **Send exactly**, your address, a QR code and a countdown such as `Send within 29:48`. 6. From your wallet, send exactly that amount to that address before the countdown ends. Use **Copy amount** and **Copy address**, or scan the QR code, which carries the amount as well. For XRP, also send the destination tag shown under **Include the destination tag** (**Copy destination tag**). 7. Select **I have sent it**. While the page is open, the card looks for your transfer every 15 seconds. Once the transfer appears, it shows its progress, for example `Seen on the network: 3 of 15 confirmations.` You can close the page after step 7. Coritan also watches your address in the background and credits the deposit without the page open. To stop the card looking, select **Stop checking**. ## Result When the transfer has all its confirmations, the card shows `Deposit credited to your balance.` and your balance goes up. The deposit appears on the **Transactions** tab as a credit with a description such as `25.05 USDC deposit on Base`, and `Crypto` under **Paid with** ([Transactions and payment attempts](/docs/billing/transactions/)). While a deposit is confirming, the **Overview** tab lists it under a notice such as `1 crypto deposit on the way`, with its confirmations so far. The credit does not pay an open invoice by itself. Coritan takes it the next time it tries to collect a renewal, or you can pay the invoice with **Account credit** at once ([Pay an invoice](/docs/billing/invoices/)). To have a deposit pay a particular invoice as soon as it arrives, quote it for that invoice with the API ([Get the amount to send](#get-the-amount-to-send)). ## How much you are credited The amount under **Send exactly** is worked out from three parts, and the line under the address lists them: The coin's rate : The market price less a spread, both shown in the quote. By default USDT and USDC have no spread and count as $1.00 each while they trade within 2 cents of $1.00. ETH and SOL have a 1.5% spread by default, and other coins 2%. The network fee : What it costs to move your deposit on that network. It differs a lot from one network to another and changes with the network's own fees. Each quote shows it, so for a small amount, compare a few coins and choose one with a low fee. A margin for price changes : For coins other than USDT and USDC, the amount includes an extra 2%, so a drop in price while you send does not leave you short. Coritan credits what arrives: its value at the rate, less the network fee. Nothing is kept back from the margin, so the credit shown in the quote (`Credits about $25.00`) is often a little more than the amount you asked for. If you send less than the quote, you receive less credit; if you send more, you receive more. The countdown is how long Coritan holds the rate: 30 minutes. A transfer that first appears on the network before the countdown ends is credited at the quoted rate, however long its confirmations take. If the price has gone up by then, you get the higher rate. If it has gone down by more than about 2%, the credit is less than quoted, because Coritan raises the new rate by no more than 2%. A transfer that appears after the countdown ends is credited at the rate at that time. A transfer below the coin's minimum is still credited, less the network fee. A transfer worth less than its own network fee credits nothing. ## Networks and addresses Your addresses are permanent: each one stays yours and receives any number of deposits. One address serves every network of the same kind. | Network | Your address | Confirmations by default | | --- | --- | --- | | Ethereum, BNB Smart Chain, Base, Polygon, Arbitrum One, OP Mainnet | One address for all six networks | Ethereum 6, BNB Smart Chain 7, Polygon 10, the others 15 | | Solana | An address of its own | 1 | | Tron | An address of its own | 10 | | Dogecoin | An address of its own | 10 | | XRP Ledger | Coritan's shared address, with a destination tag that is yours | 1 | The **Coin** list shows which coins and networks Coritan accepts at the moment, such as `USDT on Tron` or `ETH on Arbitrum One`. Bitcoin is not accepted. > [!WARNING] > Send only a coin and network from the **Coin** list. Coritan credits a listed coin on any network your address serves, but it cannot recover a coin or network that is not in the list. On the XRP Ledger, a transfer without your destination tag cannot be matched to your account. ## Troubleshooting **Crypto** is not under **Pay with** : Coritan is not taking crypto deposits at the moment. Add credit another way ([Add credit to your balance](/docs/billing/add-credit/)). **Crypto deposits are not available right now** : No coin can be credited at the moment. Pay by card or PayPal, or try again later. **Could not load the coins you can send** : The list did not load. Select **Try again**. The address shows `—` and there is no countdown : Coritan has not issued you an address for that network yet. Issue it with the API ([Get your deposit address](#get-your-deposit-address)), then select **Show where to send** again. Do not send anything until the address shows. `Choose an amount first.` : The amount is missing or outside $1.00 to $10,000.00. Choose another amount. `Could not work out how much to send` : Coritan could not quote the coin, often because it has no current price for it. The message under it gives the reason. Try again in a few minutes, or choose another coin. `The network fee would use up this deposit. Choose another coin or a larger amount.` : The amount is too small for that network's fee. Choose a network with a lower fee, or a larger amount. **We no longer hold this rate** : The countdown ended before you sent. Do not send the old amount: select **Get a new amount** and send the new one. If you already sent the old amount after the countdown ended, Coritan credits it at the current rate. A deposit is on hold : The **Overview** tab shows a notice such as `1 crypto deposit on hold`, with the reason beside each deposit. When the reason is a price Coritan cannot read, it credits the deposit on its own once the price is available again. For any other reason, or if nothing changes, select **Open a ticket**. A deposit has all its confirmations but is not credited : Wait a few minutes and check again. If it is still not credited, [contact support](/docs/support/) with the transaction hash. You sent XRP without the destination tag, or a coin that is not in the list : Coritan cannot credit it automatically. [Contact support](/docs/support/) with the transaction hash. ## Related - [Add credit to your balance](/docs/billing/add-credit/) - [Pay an invoice](/docs/billing/invoices/) - [Transactions and payment attempts](/docs/billing/transactions/) - [Turn on automatic top-up](/docs/billing/automatic-top-up/) ## With the API Paying through the API takes the same steps as the dashboard: choose a coin, make sure you have an address, get a quote, send, then follow the deposit. Every amount is in US dollars. ### List the coins you can send [`GET /billing/crypto/assets`](/docs/api/reference/client/billing/#op-get-api-v1-billing-crypto-assets) lists every coin Coritan can credit right now, one entry per coin and network: ```bash curl https://api.coritan.com/api/v1/billing/crypto/assets \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "assets": [ { "network": "base", "network_name": "Base", "asset": "USDC", "display_name": "USD Coin (BASE)", "chain_family": "evm", "decimals": 6, "is_stablecoin": true, "contract_address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "confirmations": 15, "min_deposit_usd": "0.5000", "spread_percent": "0", "market_rate_usd": "1.000000000000000000", "applied_rate_usd": "1.000000000000000000", "notes": null } ] } ``` `network` and `asset` are the values the other calls take. `applied_rate_usd` is the market rate less the spread, and `chain_family` tells you which address a coin uses. ### Compare the ways to pay an amount [`GET /billing/crypto/plan`](/docs/api/reference/client/billing/#op-get-api-v1-billing-crypto-plan) quotes an amount on every coin at once, cheapest network fee first. `recommended` is the cheapest option that can pay the amount, and an option whose network fee would use up the deposit has `viable` `false`. Add `invoice_id` to plan for an invoice's unpaid balance instead: you still send `amount`, and Coritan uses the invoice's outstanding balance when it has one. ```bash curl "https://api.coritan.com/api/v1/billing/crypto/plan?amount=25" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "usd_target": "25.00", "options": [ { "network": "base", "network_name": "Base", "asset": "USDC", "send_amount": "25.05", "send_amount_raw": "25050000", "decimals": 6, "you_receive_usd": "25.0007", "network_fee_usd": "0.0493", "fee_share": "0.0020", "market_rate_usd": "1.000000000000000000", "spread_percent": "0", "headroom_percent": "0", "confirmations": 15, "viable": true, "good_value": true, "note": "Credited at 1:1, less approximately $0.05 in network fees." }, { "network": "ethereum", "network_name": "Ethereum", "asset": "ETH", "send_amount": "0.010456", "send_amount_raw": "10456000000000000", "decimals": 18, "you_receive_usd": "25.5015", "network_fee_usd": "0.2463", "fee_share": "0.0099", "market_rate_usd": "2500.000000000000000000", "spread_percent": "1.5000", "headroom_percent": "2.0", "confirmations": 6, "viable": true, "good_value": true, "note": "Network fees of approximately $0.25 apply." } ], "recommended": {"network": "base", "asset": "USDC"}, "note": "Send the exact amount shown. You are credited what arrives less the network fee, and the amount already allows for the price moving while the transfer confirms, so you will not end up short of the total." } ``` `good_value` is `false` when the network fee is more than 5% of the amount. A plan holds no rate; get a quote before you send. ### Get your deposit address [`GET /billing/crypto/address`](/docs/api/reference/client/billing/#op-get-api-v1-billing-crypto-address) returns your permanent address for a network, and issues it the first time you ask. Asking again, or asking for another network of the same kind, returns the same address. ```bash curl "https://api.coritan.com/api/v1/billing/crypto/address?network=base" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "account_id": 4127, "chain_family": "evm", "chain_family_name": "EVM", "address": "0x4f2cexample9e8d7c6b5a4f3e2d1c0b9a8f7e6d5", "destination_tag": null, "x_address": null, "requires_destination_tag": false, "networks": ["ethereum", "bsc", "base", "polygon", "arbitrum", "optimism"], "accepted": [ { "network": "base", "network_name": "Base", "asset": "USDC", "min_deposit_usd": "0.5000", "confirmations": 15, "contract_address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" } ], "explorer_url": "https://basescan.org/address/0x4f2cexample9e8d7c6b5a4f3e2d1c0b9a8f7e6d5", "payment_uri": "ethereum:0x4f2cexample9e8d7c6b5a4f3e2d1c0b9a8f7e6d5@8453", "qr_svg": "…", "is_permanent": true } ``` `networks` lists every network the address receives on, and `accepted` every coin Coritan accepts at it. For the XRP Ledger, `requires_destination_tag` is `true`, `destination_tag` is your tag, and `x_address` holds the address and the tag in one string, so a wallet that accepts it cannot leave the tag off. ### Get the amount to send [`GET /billing/crypto/quote`](/docs/api/reference/client/billing/#op-get-api-v1-billing-crypto-quote) takes `network`, `asset` and `amount` (more than `0`, at most `100000`), and optionally `invoice_id`. The quote holds its rate for 30 minutes (`rate_lock_seconds`) until `rate_locked_until`. With `invoice_id`, Coritan puts your balance towards that invoice as soon as the deposit is credited; pass the invoice's unpaid balance as `amount`. ```bash curl "https://api.coritan.com/api/v1/billing/crypto/quote?network=base&asset=USDC&amount=25" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "network": "base", "asset": "USDC", "decimals": 6, "usd_target": "25.0000", "market_rate_usd": "1.000000000000000000", "spread_percent": "0", "applied_rate_usd": "1.000000000000000000", "send_amount": "25.05", "send_amount_raw": "25050000", "network_fee_amount": "0.05", "network_fee_usd": "0.0493", "headroom_percent": "0", "credited_estimate_usd": "25.0007", "covers_fee": true, "minimum_usd": "0.5000", "confirmations": 15, "below_minimum": false, "address": "0x4f2cexample9e8d7c6b5a4f3e2d1c0b9a8f7e6d5", "destination_tag": null, "payment_uri": "ethereum:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913@8453/transfer?address=0x4f2cexample9e8d7c6b5a4f3e2d1c0b9a8f7e6d5&uint256=25050000", "qr_svg": "…", "intent_id": 918, "rate_locked_until": "2026-09-25T14:30:00.412233+00:00", "rate_lock_seconds": 1800, "invoice_id": null } ``` Send `send_amount` (in whole coins; `send_amount_raw` is the same figure in the coin's smallest unit). `payment_uri` and `qr_svg` carry the amount, so a wallet that opens or scans them fills it in. `covers_fee` is `false` when the network fee would use up the deposit. A quote carries `address`, `rate_locked_until` and the fields after them only when you already have an address for that network. Without one, it has none of them and holds no rate: get your address first. A new quote for the same invoice, or for a top-up, replaces the one before it, so send the amount from the latest quote. ### Follow a deposit [`POST /billing/crypto/check`](/docs/api/reference/client/billing/#op-post-api-v1-billing-crypto-check) asks Coritan to look for your transfer now, and answers with your deposits. Send `network` to look only at that network's address. Coritan reads the network for an address at most once every 20 seconds; in between, the call answers from what it already knows, with `chain_checked` `false` and `next_check_in_seconds` until the next read. The call answers `429` after 12 calls in a minute. ```bash curl -X POST https://api.coritan.com/api/v1/billing/crypto/check \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"network": "base"}' ``` The answer has `chain_checked`, `next_check_in_seconds`, `found` (new transfers this read found), and `deposits` and `summary` as [`GET /billing/crypto/deposits`](/docs/api/reference/client/billing/#op-get-api-v1-billing-crypto-deposits) returns them. That call lists your deposits newest first (`limit`, default `50`, at most `200`), and `summary.confirming` holds up to five deposits that are not credited yet: ```json { "deposits": [ { "id": 7731, "network": "base", "network_name": "Base", "asset": "USDC", "amount": "25.05", "amount_raw": "25050000", "decimals": 6, "tx_hash": "0xexampletransactionhash", "explorer_url": "https://basescan.org/tx/0xexampletransactionhash", "status": "detected", "confirmations": 9, "required_confirmations": 15, "confirmation_percent": 60, "credited_usd": null, "market_rate_usd": null, "spread_percent": null, "hold_reason": null, "first_seen_at": "2026-09-25T14:09:12", "credited_at": null } ], "summary": { "total": 1, "pending": 1, "held": 0, "confirming": [ { "id": 7731, "network": "base", "network_name": "Base", "asset": "USDC", "amount": "25.05", "status": "detected", "confirmations": 9, "required_confirmations": 15, "confirmation_percent": 60, "hold_reason": null, "tx_hash": "0xexampletransactionhash", "explorer_url": "https://basescan.org/tx/0xexampletransactionhash" } ] } } ``` [`GET /billing/crypto/deposits/{deposit_id}`](/docs/api/reference/client/billing/#op-get-api-v1-billing-crypto-deposits-deposit-id) returns one deposit in the same shape. A deposit's `status` is one of these: `detected` : Seen on the network and waiting for confirmations. `held` : Confirmed, but not credited yet. `hold_reason` says why, for example `asset is not enabled for automatic credit`, or a price Coritan cannot read, such as `price for ETH is 12 minutes old`. `credited` : Added to your balance. `credited_usd` is the amount, and `0.0000` for a transfer worth less than its network fee. | Status | `detail` | When | | --- | --- | --- | | `404` | `Invoice not found` | The `invoice_id` is not one of your invoices. | | `404` | `Deposit not found` | The deposit is not one of yours. | | `409` | `USDC on base is not accepted` | Coritan does not accept that coin on that network. | | `409` | For example `price for ETH is 12 minutes old` | Coritan has no current price for the coin, so it cannot quote it. | | `409` | `unknown network dogechain` or `deposit addresses for this network are temporarily unavailable` | Coritan cannot give you an address for that network. | | `422` | A validation error | A parameter is missing, or `amount` is `0` or less or above `100000`. | | `429` | `rate_limited`, with `retry_after_seconds` | More than 12 checks in a minute. Wait for the time in the `Retry-After` header. | ## API - `GET /api/v1/billing/crypto/assets`: Assets we can accept right now (https://www.coritan.com/docs/api/reference/client/billing/#op-get-api-v1-billing-crypto-assets) - `GET /api/v1/billing/crypto/plan`: Every way to pay this amount in crypto, cheapest first (https://www.coritan.com/docs/api/reference/client/billing/#op-get-api-v1-billing-crypto-plan) - `GET /api/v1/billing/crypto/quote`: How much to send for a target USD credit, itemised (https://www.coritan.com/docs/api/reference/client/billing/#op-get-api-v1-billing-crypto-quote) - `GET /api/v1/billing/crypto/address`: This user's permanent deposit address for the network's chain family (https://www.coritan.com/docs/api/reference/client/billing/#op-get-api-v1-billing-crypto-address) - `POST /api/v1/billing/crypto/check`: Look for this customer's payment now, and say where it stands (https://www.coritan.com/docs/api/reference/client/billing/#op-post-api-v1-billing-crypto-check) - `GET /api/v1/billing/crypto/deposits`: Deposit history, including transfers still confirming (https://www.coritan.com/docs/api/reference/client/billing/#op-get-api-v1-billing-crypto-deposits) - `GET /api/v1/billing/crypto/deposits/{deposit_id}`: One deposit, for polling while it confirms (https://www.coritan.com/docs/api/reference/client/billing/#op-get-api-v1-billing-crypto-deposits-deposit-id) # How hourly billing works > How hourly services are charged against your credit, who can order them, and how the monthly cap limits what you pay. Source: https://www.coritan.com/docs/billing/hourly-billing/ An *hourly* service has a price per hour instead of a monthly or yearly price. Coritan takes that price from your credit balance each hour the service runs, and stops charging for the month once you reach the *monthly cap*. There is no renewal invoice. Container Apps servers and Cloud Compute instances can be billed hourly, when their plan has an hourly price. ## Unlocking hourly billing You can order an hourly plan once you have added at least $10.00 of credit in total, by default. Credit you add by card, PayPal, checkout page, automatic top-up or cryptocurrency all counts. Once unlocked, hourly billing stays unlocked when your balance falls again. Until then, an hourly order is refused with `You must deposit at least $10 before using hourly billing services`. [Add credit to your balance](/docs/billing/add-credit/) to unlock it. ## What you pay Ordering an hourly service raises a first invoice for one hour's price, plus any setup fee and the first period of each add-on ordered with it. After that, Coritan checks every hourly service once an hour: - A Container Apps server is charged for the hour when it is running at the check. A stopped server costs nothing. - A Cloud Compute instance is charged for every hour it exists, stopped or running, until you cancel it. Only a suspended instance costs nothing. Each charge takes one hour's price from your credit. Hourly charges are not invoiced and do not appear on the **Transactions** tab. The service's **Billing** tab shows what it has cost this month as **Used this month**, and the API lists each charge as `Hourly charge for service #311`, with your service's number. ## The monthly cap Each hourly service has a monthly cap: 672 hours of its hourly price by default, set when you order it. For example, a plan at `$0.0200/hr` caps at $13.44 a month. Once a service's charges for the month reach the cap, Coritan stops charging it until the 1st of the next month, when every hourly service starts again from zero. The service keeps running. **Used this month** shows the cap beside what you have used, for example `$4.12 of $13.44 cap`. ## When your credit runs out When your credit cannot cover the next hour, Coritan first tries an [automatic top-up](/docs/billing/automatic-top-up/), if you turned it on. If that fails or is off, Coritan suspends the service with the reason `Insufficient credit balance`. > [!IMPORTANT] > Adding credit does not resume a service that ran out. [Contact support](/docs/support/) after you add credit and ask for the service to be resumed. Turn on automatic top-up to avoid the suspension. ## Stopping the charges Cancel the service on its **Billing** tab. An hourly service ends at once and its charges stop from the next hour ([Cancel a service](/docs/billing/cancel-a-service/)). Stopping a Cloud Compute instance does not stop its charges. ## Related - [Add credit to your balance](/docs/billing/add-credit/) - [Turn on automatic top-up](/docs/billing/automatic-top-up/) - [Transactions and payment attempts](/docs/billing/transactions/) ## With the API [`GET /billing/hourly-eligibility`](/docs/api/reference/client/billing/#op-get-api-v1-billing-hourly-eligibility) says whether you can order an hourly plan: ```bash curl https://api.coritan.com/api/v1/billing/hourly-eligibility \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "eligible": false, "total_deposited": 5.0, "minimum_required": 10.0, "remaining": 5.0 } ``` `total_deposited` is all the credit you have added, `minimum_required` the amount that unlocks hourly billing and `remaining` what is still needed, all in US dollars. A service's hourly figures are in `GET /services/{service_ref}`: `amount` is the hourly price, `monthly_cap` the cap, `hourly_usage_this_month` what it has cost this month and `hourly_cap_reached` whether it has hit the cap. The hourly charges themselves are in `recent_transactions` from [`GET /billing/credit`](/docs/api/reference/client/billing/#op-get-api-v1-billing-credit), which returns your last 20 credit movements ([Billing overview](/docs/billing/#with-the-api)). ## API - `GET /api/v1/billing/hourly-eligibility`: Check hourly eligibility (https://www.coritan.com/docs/api/reference/client/billing/#op-get-api-v1-billing-hourly-eligibility) # Change a service's plan > Move a service to another plan of the same product, such as another billing cycle, and see what the switch costs before you apply it. Source: https://www.coritan.com/docs/billing/change-plan/ A service's *plan* is its price and how often you pay it. Each product in the catalogue has one plan for each billing cycle it sells, such as `RX-2 (Monthly)` and `RX-2 (Annually)`, so changing plan moves a service to another billing cycle of the same product. The size, and everything that comes with it, stays the same. Coritan prices the switch for the whole days left in your current billing cycle. To move to another size, order the new size and cancel the old service once you have moved across. [Change an instance's plan or cancel it](/docs/cloud-compute/billing/#get-a-different-size) shows how for a Cloud Compute instance. ## Before you begin - The service must be `active`. A suspended service changes plan only after you pay its open invoice ([Failed payments and suspended services](/docs/billing/failed-payments/)). - A switch can raise an invoice. Coritan pays it from your credit first, so [add credit](/docs/billing/add-credit/) or keep a [payment method](/docs/billing/payment-methods/) ready. - Read the warning under [How the switch is billed](#how-the-switch-is-billed) before you move to a longer billing cycle or away from hourly billing. ## Change the plan 1. Open the service: in the sidebar, select **Services** and then the service, or open it from its product's page. Select the **Billing** tab. 2. On the **Current plan** card, select **Change plan…**. 3. In **New plan**, choose the plan you want. Each plan in the list shows its price. 4. Select **Preview**. The preview says whether the switch is an `Upgrade` or a `Downgrade`, how many days are left in this cycle, and what the switch costs today ([What the preview shows](#what-the-preview-shows)). 5. Select **Apply change**. On a Cloud Compute instance, the dashboard first checks that the host can take the new plan. ## What the preview shows **Credit for unused time** : The value of the whole days left on your current plan: its price divided by the days in the current billing cycle, times the days left. The day in progress counts as used. **Charge for the new plan** : The new plan's price for the same days, divided over the days in the new plan's billing cycle. **Due now** : The charge less the credit. It is below zero when the new plan costs less per day. **Credit you receive** : Shown for a downgrade. The part of the difference that reaches your credit balance. By default, Coritan keeps back one day, so the preview shows the number of creditable days beside it. ## How the switch is billed Coritan bills a switch by comparing the two plans' prices for one billing cycle. The new plan has a higher price : Coritan raises an invoice, due at once, and pays as much of it as your credit covers. The new plan applies when the invoice is paid. The invoice is for **Due now** when that is above zero. When **Due now** is zero or less, the invoice is for the new plan's full price, with a line such as `Plan upgrade (first 30 days)`. That happens when you move to a longer billing cycle, which costs less per day, and when less than one day of the cycle is left. The preview shows such a switch as a `Downgrade`. The new plan has the same or a lower price : The new plan applies at once. When **Due now** is below zero, the difference goes into your credit balance, less one day by default (**Credit you receive**). When **Due now** is above zero, which happens when you move to a shorter billing cycle, Coritan raises an invoice for it that you pay yourself. Your renewal date stays the same. From that date, each renewal invoice is for the new plan's price, and the service renews on the new plan's billing cycle. > [!WARNING] > Moving to a longer billing cycle, or from hourly billing to a monthly or longer plan, charges the new plan's full price now and leaves the renewal date where it was. The renewal invoice that follows, on the old date, is for the full new price as well. For an hourly service, whose renewal date has already passed, it follows within a day. [Contact support](/docs/support/conversations/) before you make such a switch. A service you move to hourly billing is charged from your credit by the hour from then on ([How hourly billing works](/docs/billing/hourly-billing/)). It has no monthly cap: Coritan sets a cap only on a service ordered on an hourly plan. ## Result A message confirms the change: `Plan changed.` when the new plan applies at once, or `Pay the upgrade invoice to apply the plan change` when an invoice waits for payment. [Pay the invoice](/docs/billing/invoices/) on the **Invoices** tab under **Billing**, and the new plan applies as soon as it is paid. Once the new plan applies, the **Current plan** card shows it and the **Activity** table lists the change. > [!IMPORTANT] > An invoice from a plan change counts as any other unpaid invoice. If you leave it unpaid, the service is suspended one day after the invoice date by default, and later deleted ([Failed payments and suspended services](/docs/billing/failed-payments/)). Pay it, or ask [support](/docs/support/conversations/) to cancel it. ## Troubleshooting `No other plan to switch to` : The product has no other plan on sale, so **Change plan…** does not appear. To move to another size, order it as a new service. `Service is not active` : The service is suspended, still being set up, or has ended. Pay its open invoice to lift a suspension, then try again. **Could not preview the change** : The preview failed. The message under it gives the reason, such as `Pricing tier not found` when the plan is no longer on sale. Choose another plan. **Could not change the plan** : The change failed. The message under it gives the reason. `Already on this plan` means the service is on that plan now. On a Cloud Compute instance, `The host cannot apply this plan:` followed by a reason means the host refused the size, and `Could not check that the host can take the new plan. Try again in a moment.` means the check did not get an answer. The preview shows a credit, but applying raises an invoice : The new plan has a higher price for one billing cycle, so Coritan bills it as described under [How the switch is billed](#how-the-switch-is-billed). You paid the invoice, but the plan did not change : The service was suspended when the payment arrived, so the plan stayed as it was. [Contact support](/docs/support/conversations/) with the invoice number. ## Related - [Cancel a service](/docs/billing/cancel-a-service/) - [Pay an invoice](/docs/billing/invoices/) - [How hourly billing works](/docs/billing/hourly-billing/) - [Change an instance's plan or cancel it](/docs/cloud-compute/billing/) ## With the API `service_ref` is the service ID, or the UUID of the instance, server or floating IP behind it. The plans a service can move to are the other entries in its product's `pricing` list: read `product_id` from [`GET /services/{service_ref}`](/docs/api/reference/client/services/#op-get-api-v1-services-service-ref), then read the product with [`GET /products/{product_id}`](/docs/api/reference/client/catalog/#op-get-api-v1-products-product-id). Each entry's `id` is a `new_pricing_id`. ### Preview a switch [`GET /services/{service_ref}/upgrade-preview`](/docs/api/reference/client/services/#op-get-api-v1-services-service-ref-upgrade-preview) takes the plan as the query parameter `new_pricing_id`: ```bash curl "https://api.coritan.com/api/v1/services/311/upgrade-preview?new_pricing_id=42" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` This service is on a $4.99 monthly plan with 15 of 30 days left, and plan `42` is the same size at $13.47 a quarter: ```json { "credit_amount": 2.49, "charge_amount": 2.2, "net_amount": -0.29, "credit_estimate": 0.27, "creditable_days": 14, "days_remaining": 15, "days_in_cycle": 30, "change_type": "downgrade" } ``` `credit_amount`, `charge_amount` and `net_amount` are **Credit for unused time**, **Charge for the new plan** and **Due now**. `credit_estimate` is the credit a downgrade pays out over `creditable_days`. `change_type` follows the sign of `net_amount`, so it can say `downgrade` for a switch that is billed as an upgrade, as it does here. ### Apply a switch [`POST /services/{service_ref}/change-plan`](/docs/api/reference/client/services/#op-post-api-v1-services-service-ref-change-plan) takes `new_pricing_id` in the body: ```bash curl -X POST https://api.coritan.com/api/v1/services/311/change-plan \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"new_pricing_id": 42}' ``` The quarterly plan has the higher price, so Coritan raised an invoice for its full price, $13.47, and the account's credit did not cover it: ```json { "change_type": "upgrade", "net_amount": -0.29, "credit_amount": 2.49, "charge_amount": 2.2, "credit_issued": 0.0, "days_remaining": 15, "invoice_id": 5120, "status": "payment_required", "message": "Pay the upgrade invoice to apply the plan change" } ``` `status` is `applied` when the new plan applies now, and `payment_required` when it waits for the invoice in `invoice_id`. Read the amount owed from [`GET /billing/invoices/{invoice_id}`](/docs/api/reference/client/billing/#op-get-api-v1-billing-invoices-invoice-id) and pay it as [Pay an invoice](/docs/billing/invoices/#with-the-api) describes. `credit_issued` is the credit a downgrade added to your balance, in US dollars. While a plan-change invoice is open, asking again for the same plan returns that invoice. Asking for another plan with a higher price cancels it and raises a new one. | Status | `detail` | Cause | | --- | --- | --- | | `400` | `Service is not active` | The service is not `active`. | | `400` | `Already on this plan` | `new_pricing_id` is the current plan. | | `400` | `New pricing must belong to the same product` | The plan belongs to another product. | | `400` | `Plan changes must stay within the same hardware tier` | The plan belongs to a product on another hardware tier. | | `404` | `Pricing tier not found` | No plan has that ID. | | `404` | `Target product not found` | The plan's product no longer exists. | ## API - `GET /api/v1/services/{service_ref}/upgrade-preview`: Preview plan change (https://www.coritan.com/docs/api/reference/client/services/#op-get-api-v1-services-service-ref-upgrade-preview) - `POST /api/v1/services/{service_ref}/change-plan`: Change plan (https://www.coritan.com/docs/api/reference/client/services/#op-post-api-v1-services-service-ref-change-plan) # Cancel a service > Cancel a service at the end of its term or straight away, and learn what happens to its data and its unpaid invoices. Source: https://www.coritan.com/docs/billing/cancel-a-service/ Cancelling ends a service and stops its billing. You choose when it ends: at the end of the current term, so it keeps running until the time you have paid for runs out, or immediately. Coritan does not refund unused time. > [!CAUTION] > When a service ends, Coritan deletes it and the data on it. You cannot restart a service once it has ended. Copy out anything you need before you cancel. ## Before you begin - Sign in to the [dashboard](https://www.coritan.com/dashboard). - Copy your data off the service. For a Container Apps server, you can also keep a snapshot, as the steps below show. - Check what else uses the service, such as DNS records or proxies that point at its address. They stay on your account and stop working when the service ends. ## Cancel a service 1. Open the service's **Billing** tab. In the sidebar, select **Services** and then the service. For a Container Apps server, select **Plan and billing** on the server's **Settings** tab. A snapshot storage service opens the **Snapshots** page instead, which has no billing tab: go to `https://www.coritan.com/dashboard/services//billing`, with the number from the **Services** list. 2. On the **Cancel service** card, select **Cancel service…**. The **Cancel this service?** dialog opens and says what the product deletes when the service ends. 3. Under **When**, choose: - **At the end of the current term** to keep the service running until the date the dialog shows. This is the default. - **Immediately** to end it now. An hourly service has no choice. It ends at once. 4. For a Container Apps server, leave **Take a snapshot first** ticked to keep a snapshot of the server, or clear it to keep nothing. The box also appears for a Cloud Compute instance, where it keeps nothing. 5. Type `cancel` and select **Cancel service**. ## Result A message confirms the cancellation, and Coritan emails you about it. The **Activity** table on the **Billing** tab lists the request. **At the end of the current term** : The message reads `Scheduled for end-of-term termination; service remains active until then`. The **Billing** tab shows **Cancellation scheduled** with the end date, and the **Services** list shows the service as **Ending**. The service keeps working until that date, Coritan raises no more renewal invoices for it, and deletes it once the date has passed. **Immediately** : The message reads `Immediate termination queued`. The service's status becomes `pending_termination` while Coritan removes it, and then `terminated`. A service that was never set up, such as an order you never paid for, ends at once with `Service cancelled before provisioning`. Either way, Coritan cancels every unpaid invoice for the service, so you do not owe them. That includes an invoice you have partly paid, and Coritan does not return the part you paid. When you keep a snapshot, Coritan takes it at the moment you cancel, so after an end-of-term cancellation it holds the server as it was that day. It is named `Saved before cancellation` with the date, and you find it under **Snapshots** ([Take a server snapshot](/docs/managed-containers/snapshots/)). ## What ends with the service The dialog names what each product deletes: | Service | When it ends | | --- | --- | | Container Apps server | The server and its files are deleted. A snapshot you chose to keep stays under **Snapshots**. | | Cloud Compute instance | The instance is stopped, and its disk, with every snapshot on it, is deleted ([Change an instance's plan or cancel it](/docs/cloud-compute/billing/#cancel-the-instance)). | | Floating IP | The address goes back to the pool, and anything pointing at it stops resolving. | | Mail Hosting and SMTP Relay | Mailboxes, aliases and stored mail are deleted. | | Object Storage | Buckets and access keys stop working. Coritan keeps the objects for the number of days the dialog shows, and then deletes them. | | Game proxy | The join address stops resolving. Your own server is not touched. | ## Orders you have not paid for You do not need to cancel an order you decided against. A Container Apps server or floating IP order that is still unpaid and not set up after 48 hours, by default, is cancelled on its own, along with its invoice. Any other unpaid order stays on your account until you cancel it with **Immediately**. ## Troubleshooting The **Cancel service** card is missing : A cancellation is already scheduled, or the service has ended. The **Cancellation scheduled** alert shows the end date. `Service already scheduled for end-of-term termination` : You cancelled the service before, at the end of its term. To end it sooner, [contact support](/docs/support/conversations/). `Service already cancelled/terminated` : The service has ended or is being removed. **Could not cancel the service** : The cancellation failed. The message beside it gives the reason. Try again, or [contact support](/docs/support/conversations/). You want to keep a service that is scheduled to end : [Contact support](/docs/support/conversations/) before the end date. You cannot undo a cancellation yourself. There is no `Saved before cancellation` snapshot : Coritan skips the snapshot when the server has a recent snapshot already, and when it cannot take one. Look under **Snapshots** for the most recent snapshot of the server. ## Related - [Change a service's plan](/docs/billing/change-plan/) - [Pay an invoice](/docs/billing/invoices/) - [How hourly billing works](/docs/billing/hourly-billing/) - [Manage your services](/docs/get-started/services/) ## With the API [`POST /services/{service_ref}/cancel`](/docs/api/reference/client/services/#op-post-api-v1-services-service-ref-cancel) cancels a service. `service_ref` is the service ID, or the UUID of the instance, server or floating IP behind it. Every field of the body is optional: `immediate` : `true` ends the service now. `false`, the default, ends it at the end of its term. Send `true` for an hourly service, as the dashboard does. `keep_snapshot` : `true`, the default, snapshots a Container Apps server before anything else happens. It has no effect on other products. `reason` : Your reason, recorded on the service's history. ```bash curl -X POST https://api.coritan.com/api/v1/services/311/cancel \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"immediate": false, "keep_snapshot": true, "reason": "Moved to a larger server"}' ``` ```json { "message": "Scheduled for end-of-term termination; service remains active until then", "immediate": false, "status": "active", "termination_date": "2026-10-10T00:00:00", "snapshot_taken": true } ``` `status` is the service's status after the request: `active` until an end-of-term date, `pending_termination` while an immediate cancellation runs, or `terminated` for a service that was never set up. `termination_date` is when the service ends, in UTC. `snapshot_taken` says whether Coritan took the snapshot. | Status | `detail` | Cause | | --- | --- | --- | | `400` | `Service already cancelled/terminated` | The service has ended or is being removed. | | `400` | `Service already scheduled for end-of-term termination` | The service is already set to end at the end of its term. | | `403` | `Access denied` | The service belongs to another account. | | `404` | `Service not found` | No service has that reference. | ## API - `POST /api/v1/services/{service_ref}/cancel`: Cancel service (https://www.coritan.com/docs/api/reference/client/services/#op-post-api-v1-services-service-ref-cancel) # Failed payments and suspended services > Why a payment fails, how Coritan retries it, when a service is suspended, and how to get the service running again. Source: https://www.coritan.com/docs/billing/failed-payments/ When Coritan cannot collect an invoice, it retries the charge, then suspends the services on the invoice, and in the end deletes them. Paying the invoice in full at any point before the deletion stops this and brings the services back. [Hourly services](/docs/billing/hourly-billing/#when-your-credit-runs-out) have no renewal invoice and follow their own rule. With the default settings, the steps fall on these days. Coritan runs each check once a day, so a step can come up to a day later than the table says. | When | What Coritan does | The email's subject | | --- | --- | --- | | Up to 3 days before the due date | Raises the renewal invoice, takes what it can from your credit, and charges your default payment method for the rest. | `Your Coritan invoice INV-20260925-48213 is ready` | | After a charge fails | Tries the charge again on its own. | `Payment failed for invoice INV-20260925-48213` | | 1 day after the due date | Suspends the services on the invoice. | `Your services have been suspended` | | 4 days after the due date | Reminds you. | `Urgent: update your payment method for INV-20260925-48213` | | 3 days after the suspension | Sets a date to delete each service, at least 3 days ahead, and takes a snapshot of each Container Apps server. | `Action needed: we will delete your server on`, then the date | | 6 days after the due date | Sends a last reminder. | `Final notice for invoice INV-20260925-48213`, then the days left | | On the deletion date | Deletes the services and cancels their unpaid invoices. | `Your server has been deleted` | The reminders start only after a charge has failed. When Coritan has nothing to charge, the invoice email and the emails about the deletion are the only ones you get ([No payment was attempted](#no-payment-was-attempted)). ## A payment was declined Cause : Your bank or PayPal refused the charge. The `Payment failed` email gives the reason after `Error:`, such as `Insufficient funds` or `Card has expired`. Fix : Pay the invoice now with credit or another method ([Pay an invoice](/docs/billing/invoices/)). You can also save a method that works on the **Payment methods** tab of **Billing** ([Manage payment methods](/docs/billing/payment-methods/)), and Coritan charges it at the invoice's next try. Coritan retries a decline that can succeed later, such as `Insufficient funds` or `Card declined`: 4 hours after the first try, then at gaps that grow from a day. After a decline that cannot succeed, such as `Card has expired`, `Card reported lost` or `Bank will not honor`, it stops and starts again only when you add a payment method. The API lists every try with its reason ([Payment attempts](/docs/billing/transactions/#payment-attempts)). When the reason is `Payment submitted, awaiting settlement`, nothing was declined. Some payment types take days to settle, and the invoice is paid once the payment clears. Do not pay it again. ## Your bank asks you to confirm a payment Cause : Your bank wants the cardholder to approve the charge, for example with a code, and a charge Coritan makes on its own cannot do that. The `Payment failed` email gives a reason that starts with `Customer authentication required`. Fix : Pay the invoice yourself: in the sidebar, select **Billing**, then the **Invoices** tab, and select **Pay** beside the invoice. When the card form reads **Confirm with your bank**, complete your bank's check ([Pay an invoice](/docs/billing/invoices/)). ## No payment was attempted Cause : Your account has no saved payment method and not enough credit, so Coritan has nothing to charge. The reminders about failed charges never start, so the next email after the invoice is the one that gives the deletion date. Fix : Pay the invoice before its due date ([Pay an invoice](/docs/billing/invoices/)). To have Coritan pay renewals for you, save a payment method on the **Payment methods** tab; Coritan charges it at the invoice's next try. When a card and a PayPal account both show `Default`, Coritan cannot choose between them, and [Manage payment methods](/docs/billing/payment-methods/#troubleshooting) explains the fix. ## A service is suspended Cause : An invoice for the service was still unpaid 1 day after its due date, by default. The **Suspended** box on the service's page gives the reason, such as `Unpaid invoice INV-20260925-48213`, and the server's or instance's own page shows **This server is suspended** or **This instance is suspended**. The **Suspended** filter of **Services** lists every suspended service. Fix : Select **Open invoices** in the **Suspended** box, or **Open Billing** in the server's or instance's alert, and pay each unpaid invoice for the service. Coritan lifts the suspension once an invoice is paid in full. A part payment, such as credit that covers only some of it, does not lift it. A suspended service stops working: a server or instance is shut down and cannot be started, and a floating IP is detached. Coritan keeps the data until the deletion date. A service suspended with the reason `Insufficient credit balance` is an hourly service whose credit ran out, and adding credit does not resume it. [Contact support](/docs/support/conversations/) after you add credit, as [How hourly billing works](/docs/billing/hourly-billing/#when-your-credit-runs-out) explains. For any other reason, [contact support](/docs/support/conversations/). ## The service is still suspended after you paid The payment has not cleared : The message after paying read `Payment submitted. The invoice updates once your bank settles it.` Coritan lifts the suspension when the payment clears. Do not pay again. Another invoice for the service is unpaid : Select **Billing**, then **Invoices** and **Unpaid**, and pay the rest. Any invoice for the service that is still unpaid a day after its due date suspends it again at the next daily check. A Container Apps server is stopped : A server comes back stopped. Open it and select **Start**. A Cloud Compute instance starts again on its own. A floating IP is detached : The suspension detached it. [Attach it again](/docs/floating-ips/attach-and-detach/). None of these : [Contact support](/docs/support/conversations/) with the invoice number. When you pay after the reminders have started, the receipt's subject is `Payment received and services restored`. ## An email says the service will be deleted Cause : The service has been suspended for 3 days, by default, and its invoice is still unpaid. Coritan has set the deletion date that the email gives, at least 3 days ahead. The service's page shows **Cancellation scheduled** with that date, and **Services** marks it **Ending**. For a Container Apps server, Coritan also takes a snapshot whose name starts with `Saved before removal for non-payment`, marked **By the platform** on the **Snapshots** page. Fix : Pay the invoice before the date ([Pay an invoice](/docs/billing/invoices/)). Paying cancels the deletion and lifts the suspension. The **Cancellation scheduled** box asks you to contact support, but paying is enough. If a payment is still settling on the deletion date, Coritan waits a day before it deletes the service. Once the suspension lifts, Coritan deletes its own snapshot of the server unless you locked it ([Lock, download and delete snapshots](/docs/snapshots/manage-snapshots/)). > [!CAUTION] > Coritan cannot bring back a deleted service. Everything on it is lost, apart from the snapshot of a Container Apps server. ## A service was deleted for non-payment Cause : The deletion date passed and the invoice was still unpaid. Coritan deleted the service, cancelled its unpaid invoices so that you owe nothing on them, and sent the email `Your server has been deleted`. Fix : Order the service again ([Order a service](/docs/get-started/order-a-service/)). For a Container Apps server, [start the new server from the snapshot](/docs/snapshots/new-server-from-a-snapshot/). The snapshot stays on your account after the deletion, and the email gives the date it expires when it has one ([How snapshot storage works](/docs/snapshots/storage-allowance/)). ## Related - [Pay an invoice](/docs/billing/invoices/) - [Manage payment methods](/docs/billing/payment-methods/) - [Add credit to your balance](/docs/billing/add-credit/) - [Turn on automatic top-up](/docs/billing/automatic-top-up/) - [Transactions and payment attempts](/docs/billing/transactions/) - [Manage your services](/docs/get-started/services/) ## With the API List what you owe with [`GET /billing/invoices`](/docs/api/reference/client/billing/#op-get-api-v1-billing-invoices) and `status_filter=open`: ```bash curl "https://api.coritan.com/api/v1/billing/invoices?status_filter=open" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` Read a service's state with [`GET /services/{service_ref}`](/docs/api/reference/client/services/#op-get-api-v1-services-service-ref): ```bash curl https://api.coritan.com/api/v1/services/311 \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` The answer, cut down to the fields that matter here: ```json { "id": 311, "hostname": "survival-smp", "status": "suspended", "suspension_reason": "Unpaid invoice INV-20260925-48213", "termination_date": "2026-10-04T03:05:00" } ``` `status` is `suspended` while the service is suspended, `suspension_reason` says why, and `termination_date` is the deletion date once Coritan has set one. [`GET /payments/attempts`](/docs/api/reference/client/payments/#op-get-api-v1-payments-attempts) lists your last 50 payment attempts. A declined charge has `status` `failed`, the reason in `error_message`, and `decline_type` `soft` when a retry can succeed or `hard` when it cannot ([Payment attempts](/docs/billing/transactions/#payment-attempts)). To pay, charge a saved method or use your credit, as [Pay an invoice](/docs/billing/invoices/#with-the-api) shows. # Currencies and countries > The currencies you can pay in, how your account's currency is chosen, and what your billing country changes. Source: https://www.coritan.com/docs/billing/currencies-and-regions/ Coritan prices every service in US dollars, and your invoices and your credit balance are in US dollars too. You can pay in another currency: Coritan converts the US dollar amount at its current exchange rate when you pay. Your billing country decides which payment provider takes your payments. ## Your account's currency Your account's currency is the currency Coritan charges your card or PayPal account in. It is set like this: - When you create your account, Coritan looks up the country you are connecting from. If Coritan takes payments in that country's currency, it becomes your account's currency. Otherwise your account uses US dollars. - If the lookup fails, your account starts in US dollars. The first time you open **Billing** or **Settings** from a country Coritan can place, it switches to that country's currency once, if Coritan takes payments in it. - Once you choose a currency yourself, Coritan never changes it. To choose one, change **Currency** under **Billing details** on the **Profile** tab of **Settings** ([Update your profile](/docs/account/profile/#change-your-currency-or-country)). The list shows every currency you can pay in, for example `EUR (Euro)`. Your credit balance stays in US dollars whatever you choose. ## What each payment is charged in The dashboard shows every amount in US dollars. What your bank or PayPal charges depends on how you pay: | Payment | Charged in | | --- | --- | | An invoice paid with a saved card or PayPal account | Your account's currency | | An invoice paid with a new card, PayPal or a checkout page in the **Pay** dialog | US dollars | | Credit you add with **Add credit** | Your account's currency | | Automatic renewal charges and automatic top-ups | The currency the payment method was saved in. Methods saved on the **Payment methods** tab are saved in US dollars. | | An invoice paid with account credit | Nothing is charged. Credit is already in US dollars. | Your statement shows the converted amount, and your bank may add its own fee for a payment in a foreign currency. ## Exchange rates Each rate is the market rate for that currency, plus any margin Coritan adds for it. Coritan updates the rates every hour by default. A payment is converted when you make it, so the same invoice can cost a slightly different amount in your currency on another day. A currency Coritan has just added appears in the list once it has a rate. ## Your billing country Your billing country is the country Coritan treats you as paying from. It decides which of Coritan's payment provider accounts takes a new payment, so it can change the ways to pay that you see. Charges to a card or PayPal account you have already saved do not depend on your country. Coritan decides your billing country in this order: 1. A country you chose under **Billing details**, or that Coritan support set for you. A lookup never replaces it, so it stays put when you travel. 2. The country you are connecting from, when Coritan can tell. A country that came from a lookup follows you. 3. The country Coritan last detected for you. 4. The United States, when nothing else is known. Your country does not change what you pay: the **Tax** line on every Coritan invoice is `$0.00`. ## With the API ### List the currencies you can pay in [`GET /payments/currencies`](/docs/api/reference/client/payments/#op-get-api-v1-payments-currencies) returns every currency you can be charged in, with its rate: ```bash curl https://api.coritan.com/api/v1/payments/currencies \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json [ { "base_currency": "USD", "quote_currency": "USD", "rate": "1", "market_rate": "1", "upcharge_percent": "0", "is_enabled": true, "decimal_places": 2, "name": "US Dollar", "source": "base", "fetched_at": null }, { "base_currency": "USD", "quote_currency": "EUR", "rate": "0.85411765", "market_rate": "0.85411765", "upcharge_percent": "0.0000", "is_enabled": true, "decimal_places": 2, "name": "Euro", "source": "frankfurter", "fetched_at": "2026-09-25T09:15:02", "updated_at": "2026-09-25T09:15:02" } ] ``` `quote_currency` and `name` : The currency's three-letter code and its name. `rate` : How many units of the currency Coritan charges for one US dollar. It is `market_rate` with `upcharge_percent` added on top. Rates are decimal strings. `decimal_places` : How many decimal places the currency has. A payment's `amount` is in the smallest unit, so `1299` is 12.99 in a currency with two places and 1,299 in one with none. `source` and `fetched_at` : Where the market rate came from, and when. ### List the countries [`GET /payments/countries`](/docs/api/reference/client/payments/#op-get-api-v1-payments-countries) returns the countries you can choose, your billing country and the country Coritan detects for this request. The example is shortened to two countries: ```json { "selected": "GB", "detected": "GB", "regions": {"NA": "North America", "LATAM": "Latin America & Caribbean", "UK": "United Kingdom", "EU": "European Union & EEA", "EUROPE": "Rest of Europe", "APAC": "Asia-Pacific", "MEA": "Middle East & Africa"}, "countries": [ {"code": "IE", "name": "Ireland", "currency": "EUR", "region": "EU"}, {"code": "GB", "name": "United Kingdom", "currency": "GBP", "region": "UK"} ] } ``` `countries` is sorted by name. Each country's `currency` is its own currency, which Coritan may not take payments in; the currency Coritan suggests falls back to US dollars then. `detected` is `null` when Coritan cannot place the request. ### Suggest a currency without signing in [`GET /payments/geo-currency`](/docs/api/reference/client/payments/#op-get-api-v1-payments-geo-currency) needs no token. It answers with the country Coritan places the request in and the currency it would suggest: ```bash curl https://api.coritan.com/api/v1/payments/geo-currency ``` ```json { "country_code": "GB", "suggested_currency": "GBP", "base_currency": "USD", "client_ip": "203.0.113.24" } ``` `client_ip` is the address Coritan saw the request come from. ### Read or change your currency and country [`GET /payments/preference`](/docs/api/reference/client/payments/#op-get-api-v1-payments-preference) returns your currency and country: ```json { "preferred_payment_currency": "GBP", "base_currency": "USD", "credit_currency": "USD", "auto_pay_enabled": true, "country_code": "GB", "currency_source": "geo", "country_source": "geo", "billing_country": "GB", "suggested_currency": "GBP" } ``` `preferred_payment_currency` : Your account's currency. `country_code` and `billing_country` : The country saved on your account (or `null`), and the billing country Coritan uses. `currency_source` and `country_source` : How each was set: `default`, `geo` (from your location), `user` (you chose it) or `admin` (Coritan support set it). `suggested_currency` : The currency Coritan would pick for your billing country. Reading your preference fills in your country and currency from your location in the same way as opening **Billing**. It never replaces a choice you made. Change them with [`PATCH /payments/preference`](/docs/api/reference/client/payments/#op-patch-api-v1-payments-preference). Send `currency`, `country_code` or both. Either counts as your own choice, so a later lookup leaves it alone: ```bash curl -X PATCH https://api.coritan.com/api/v1/payments/preference \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"currency": "EUR", "country_code": "IE"}' ``` ```json { "preferred_payment_currency": "EUR", "base_currency": "USD", "auto_pay_enabled": true, "country_code": "IE", "country_source": "user", "currency_source": "user", "message": "Payment preference updated" } ``` It answers `400` with `Currency TRY is not enabled` for a currency Coritan does not take, and `400` with `Unknown country` for a code it does not know. The same endpoint turns auto-pay on and off ([Manage payment methods](/docs/billing/payment-methods/#with-the-api)). ## API - `GET /api/v1/payments/currencies`: Payment currencies a customer can actually be charged in (https://www.coritan.com/docs/api/reference/client/payments/#op-get-api-v1-payments-currencies) - `GET /api/v1/payments/countries`: Countries a customer can bill from, with the one we think they are in (https://www.coritan.com/docs/api/reference/client/payments/#op-get-api-v1-payments-countries) - `GET /api/v1/payments/geo-currency`: Unauthenticated country → suggested pay currency (clamped to enabled) (https://www.coritan.com/docs/api/reference/client/payments/#op-get-api-v1-payments-geo-currency) # Container Apps > Run game servers, databases and apps on Container Apps, with a console, file manager, backups, schedules and more. Source: https://www.coritan.com/docs/managed-containers/ In the dashboard: - /solutions/containers: https://www.coritan.com/solutions/containers - /dashboard/servers: https://www.coritan.com/dashboard/servers Container Apps runs a game server, a database or an application for you in its own container. You choose the software when you order, we install it and give it an address, and you run it from the dashboard with a console, a file manager, SFTP, snapshots, schedules and shared access. The dashboard calls each one a *server*. It suits Minecraft worlds and other game servers, databases such as MariaDB, PostgreSQL, MongoDB and Redis, and Node.js or Python apps. The **Software** section of the order page lists everything you can run today. If you need a whole virtual machine with root access instead, use [Cloud Compute](/docs/cloud-compute/). ## Your servers The **Container Apps** page in the [dashboard](https://www.coritan.com/dashboard/servers) lists every server you own and every server someone has shared with you. Each row shows the server's name, status, tags, address, software and location, with meters for **Memory**, **CPU** and **Disk**. - Filter the list with **All**, **Running**, **Stopped**, **Installing** or **Suspended**, or select a tag. With more than five servers, a search box finds a server by name, address or tag. - Select **Copy address** on a row to copy the address players or apps connect to. - Select **Order server** to open the [order page](/docs/managed-containers/order-a-server/), where you can also add a dedicated address and more snapshot storage to the order. Select a server's name to open it. The top of the server page shows its status, software, location, server ID (**Copy server ID**) and address, the power buttons, and live **CPU**, **Memory**, **Disk**, **Network in** and **Uptime** figures. The tabs below hold everything else. The public page at `https://www.coritan.com/solutions/containers` describes the product. Order from the dashboard's order page, which shows the plans, software and locations that are actually available. ## What you can do Get started: - [Order a server](/docs/managed-containers/order-a-server/) - [Host a Minecraft server](/docs/managed-containers/host-a-minecraft-server/), from order to first join - [How free servers work](/docs/managed-containers/free-servers/) Run the server: - [Use the console and power controls](/docs/managed-containers/console/) - [Server settings](/docs/managed-containers/settings/): address, resources, startup command and tags - [Change the server software](/docs/managed-containers/software/), with [plugins, mods and modpacks](/docs/managed-containers/software/plugins-and-mods/), [installed addons](/docs/managed-containers/software/installed-addons/), [Java settings](/docs/managed-containers/software/java-settings/), a [resource pack](/docs/managed-containers/software/resource-pack/), [safe mode](/docs/managed-containers/software/safe-mode/), [Bedrock crossplay](/docs/managed-containers/software/bedrock-crossplay/) and [Bedrock packs](/docs/managed-containers/software/bedrock-packs/) - [Manage server ports](/docs/managed-containers/ports/) Files and data: - [Manage server files](/docs/managed-containers/files/) and [connect with SFTP](/docs/managed-containers/sftp/) - [Import a server from another host](/docs/managed-containers/import/) - [Create and manage server databases](/docs/managed-containers/databases/) - [Take a server snapshot](/docs/managed-containers/snapshots/), then [manage and restore snapshots](/docs/snapshots/) for your whole account - [Manage server backups](/docs/managed-containers/backups/) made before snapshots replaced them Automate and share: - [Schedule server tasks](/docs/managed-containers/schedules/) - [Install server updates](/docs/managed-containers/updates/) and [create server rules](/docs/managed-containers/rules/) - [Share a server with other users](/docs/managed-containers/users/) Reference: - [Server limits and rate limits](/docs/managed-containers/limits/) - [Troubleshoot servers](/docs/managed-containers/troubleshooting/) ## How servers are billed Each server is a service on the plan you chose when you ordered it, billed on its cycle (monthly, a longer term, or hourly). The order page shows each plan's price for the cycle you pick. You change the plan or cancel the server from its service page, which the **Plan and billing** link on the server's **Settings** tab opens. [Billing](/docs/billing/) explains invoices, credit and cancellation. ## Limits - The plan sets each server's memory, CPU, disk, and its database, backup and port slots. The **Resources** card on the server's **Settings** tab shows them. - Snapshots draw on one allowance for your whole account. [How snapshot storage works](/docs/snapshots/storage-allowance/) explains how big it is. - Some actions, such as power signals, are rate limited per server. [Server limits and rate limits](/docs/managed-containers/limits/) lists them. ## With the API `GET /api/v1/client/servers` lists the servers you own or share, the same list the **Container Apps** page shows. Add `?tag=` with one of your tags to list only the servers that carry it. For a server someone shared with you, `owner_email` is `null`. ```bash curl https://api.coritan.com/api/v1/client/servers \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` Each item carries the server's `uuid`, `name`, `status`, `ip_address` and `port`, its plan limits (`memory_mb`, `disk_mb`, `cpu_percent`, `allocation_limit`, `database_limit`, `backup_limit`), `template_name` for its software, `location_name`, `service_id` and `tags`. Use the `uuid` in every other server request. `GET /api/v1/client/servers/live` returns the power state and current usage of several servers in one request. Pass up to 50 server UUIDs, separated by commas, in the `uuids` parameter; without it you get your 50 newest servers. ```bash curl "https://api.coritan.com/api/v1/client/servers/live?uuids=$SERVER_A,$SERVER_B" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` The response holds `items`, one per server with its `uuid`, `state` and usage, and `degraded`, which is `true` when at least one server's state is `unknown` because it could not be read in time. `GET /api/v1/client/servers/{uuid}` returns one server with the same fields, plus `variables` (its startup variables) and `startup_command`. A server you cannot see answers `404` with `Server not found`. A server that is being deleted answers `409` with the reason `server_deleting`. ## API - `GET /api/v1/client/servers`: List servers owned by or accessible to current user (https://www.coritan.com/docs/api/reference/client/container-apps/servers/#op-get-api-v1-client-servers) - `GET /api/v1/client/servers/live`: State and usage for the servers on the list, one request instead of one per server (https://www.coritan.com/docs/api/reference/client/container-apps/servers/#op-get-api-v1-client-servers-live) - `GET /api/v1/client/servers/{uuid}`: Get server details (https://www.coritan.com/docs/api/reference/client/container-apps/servers/#op-get-api-v1-client-servers-uuid) # Order a server > Order a Container Apps server on its order page, with a plan, the software to run, a location, a name and any add-ons. Source: https://www.coritan.com/docs/managed-containers/order-a-server/ In the dashboard: - /dashboard/order/managed-containers: https://www.coritan.com/dashboard/order/managed-containers The **Container Apps** order page asks five things, in numbered sections: **Plan**, **Software**, **Location**, **Settings** and **Add-ons**. The plan and the location start with an answer chosen for you, the name follows the software you choose, and the summary beside them keeps the price up to date. Add-ons are other products bought in the same order, such as a dedicated address. Once the first invoice is paid, we install the software and set the server up for you. ## Before you begin - [Create a Coritan account](/docs/get-started/create-an-account/) and sign in. - Credit on your account pays the first invoice when you place the order. Without enough credit, you pay the rest by card or PayPal on the page the order opens. To pay in one go, [add credit](/docs/billing/add-credit/) first. - Hourly billing needs a minimum total of deposits on your account before you can choose it. [How hourly billing works](/docs/billing/hourly-billing/) explains the rule. ## Choose a plan 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and select **Order server**. You can also select the **Container Apps** card on the **Order a service** page, or one of its quick starts, which chooses the software for you. Either way, the order page opens at `https://www.coritan.com/dashboard/order/managed-containers`. 2. At the top of **Plan**, choose how often to pay. The switch lists the billing cycles the plan sells, and a longer term names what it saves against paying monthly. 3. Choose a **Hardware tier**, if the page offers more than one. The tier sets the CPU class and the storage, and each tier sells the same sizes. 4. Under **Size**, choose a plan. Each row shows its price for the cycle, its vCPUs, memory and storage, and how many ports, backups and databases it includes. We start with the recommended plan, or else the smallest one on sale. A plan marked **Sold out** cannot be ordered anywhere right now. When you choose hourly billing, a note under the sizes explains that hourly services draw on your credit as they run. If your account has not deposited enough yet, the note names the deposit it still needs, and **Top up credit** takes you to Billing to add it. ## Choose the software 1. Under **Software**, find what the server runs in the **Catalogue**. Choose a category, such as **Game servers** or **Databases**, or type in **Search the catalogue**, then select the software's card. 2. For software with flavours, such as Minecraft, choose the flavour, then the game version. For software sold in major versions, such as MariaDB, choose the version to run. 3. Under **Version**, leave **Latest** selected to install the newest release, or choose a version. With many versions, **Find a version** filters them. For Minecraft, we choose the Java runtime each version needs. 4. If the software asks for more details, such as a Git repository under **Source**, fill in the required fields. The list of versions leaves out snapshots and unsupported builds. You can install those later from the server's **Software** tab, where you can also switch to other software. ## Choose a location and a name 1. Under **Location**, choose a data centre on the map or in the list under **Data centres**, which groups them by region. We start with the one marked **Closest to you**. Pick the one closest to the people who will use the server: its address is local to that location. A location marked **Sold out** has no room for this plan right now. 2. Under **Settings**, keep the suggested **Server name**, such as `Minecraft Paper server`, or type your own of 2–60 characters. The name appears in your server list and the console. The dashboard has no way to rename a server later, so choose it with care. 3. If the plan has **Plan options**, answer them. We add the price of any option you take to every charge. > [!NOTE] > We assign the server's public address and first port while we set it up in the location you chose. You can add more ports from the [Ports tab](/docs/managed-containers/ports/), up to the plan's limit. ## Add products to the order Under **Add-ons**, turn on the switch of anything you want to buy with the server. This is optional. Each card shows its price for the order's billing cycle, and everything you add goes on the server's first invoice: - A **Dedicated IPv4 address** is an address of the server's own, so players join on the default port and nobody else shares it. A server takes one. It comes from the address pool in the server's location, and we attach it to the server when it is ready. It stays on your account if you move it later. - A **DDoS Shield profile** gives the dedicated address your own protection mode, firewall rules and packet rate limits, so it needs the dedicated address in the same order. Give it a **Profile name**, or we name it after the server. - **Snapshot Storage** adds room for snapshots of your servers, on top of what the plan includes. Choose a size under **Size** when more than one is on sale. - **SMTP Relay** delivers the mail your applications send. Choose a plan if there is more than one, and add a **Sending domain** now or later. - **Mail Hosting** adds mailboxes on your own domain. Choose a plan if there is more than one, and add a **Domain** now or later. A card that cannot be added says why, and its switch stays off. For example, the pool in the server's location may have no addresses left. Each add-on becomes a service of its own, which you can cancel on its own later. [Add products to the order](/docs/get-started/order-a-service/#add-products-to-the-order) has the rules for every product. ## Place the order 1. Check the summary under **Your order**. On a phone it follows the sections, and **Review order** at the bottom of the screen takes you there. It lists the plan, the software, the location and the name, each with **Change** beside it to go back to its section. Under **Billed** it lists the plan and each add-on with its price. 2. Check the **Total**. It shows the price for each billing cycle, any one-time setup fee, what a longer term saves and what is due today. The line under it says how the first invoice will be paid. 3. If the button is greyed out, read the line under it. It names the first answer still missing, such as `Choose the software to run.`, and selecting it takes you to that section. 4. Select the button. It reads **Deploy server** when your credit pays the first invoice or your account is billed in arrears, and **Place order and pay** when you pay on the next page. If we refuse the order, **Could not place the order** appears above the button with the reason, and your choices stay as they were. Otherwise the order's own page opens. When its title reads **Order placed, payment due**, pay under **Pay invoice**, as [Pay and follow the order](/docs/get-started/order-a-service/#pay-and-follow-the-order) describes. If you leave without paying, pay the invoice under [Invoices](/docs/billing/invoices/). Setup starts once it is paid. We cancel a server order whose first invoice is still unpaid after two days, by default. ## Result The order's page shows **Setting up** while we set the server up, and its title becomes **Your order is ready** when the server and every add-on are active. The server appears on the **Container Apps** page with the status `Installing`, and we email you when each service is ready. The install does not start the server, although the order's page says it boots when the install finishes. When the install is done, open the server (reload the page if the console has not opened) and select **Start**. ## Troubleshooting The button under the summary is greyed out : The line under it names the first answer still missing. Select it to go to that section. `This plan has no active price.` : The plan has no price on sale on any billing cycle. Choose another plan. `This plan is sold out everywhere right now.` : No location has room for this plan. Choose another size or hardware tier. `Hourly billing needs a deposit first.` : The note under **Size** names the deposit your account still needs. Select **Top up credit** to add it, or choose another cycle. `Fill in the software's required fields.` : The software needs details before we can install it, such as a Git repository under **Source**. Fill in each required field under **Software**. **Could not load the catalogue** : The list of software did not load. Select **Try again**. If it keeps failing, support can place the order for you. **Could not load the versions** : The version list did not load. The order still installs the latest release, and you can change the version later from the server's **Software** tab. `This application needs a database. Order a MariaDB server as well, or point it at one you already run.` : The software you chose stores its data in MariaDB. Order a second server that runs MariaDB, or use a database you already have. `That location has no capacity for this plan.` : The location filled up after you chose it. Choose another location, or a different size or hardware tier. Capacity differs by plan. `Give the server a name.` : The **Server name** field is empty. Type a name of 2–60 characters. The **Dedicated IPv4 address** card says the pool is sold out : The location has no addresses left to sell. Choose another location, or order the server without one and [order a floating IP](/docs/floating-ips/order-a-floating-ip/) later. The **DDoS Shield profile** card says `Add the dedicated IPv4 address first.` : A profile protects addresses you own. Turn on the **Dedicated IPv4 address** card first. ## Related - [Host a Minecraft server](/docs/managed-containers/host-a-minecraft-server/) walks through the whole order for a Minecraft world. - [Start a new server from a snapshot](/docs/snapshots/new-server-from-a-snapshot/) orders a server that starts with a saved copy of another server's files. - [Order a service](/docs/get-started/order-a-service/) explains ordering, payment and add-ons for every product. ## With the API Two requests list what the **Software** section offers. `GET /api/v1/client/containers/catalog` returns every piece of software you can order, each with its `slug`, `name`, `description`, `category` and `versioned`. Add `?category=` to narrow the list. ```bash curl https://api.coritan.com/api/v1/client/containers/catalog \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` `GET /api/v1/client/containers/catalog/{slug}/compose` returns the versions of one piece of software. Each item in `versions` has a `version_id` and a `name`, and for Minecraft Java software also `required_java` and `recommended_runtime_slug`. Add `include_snapshots=true` to include test releases, and `limit` (1–500, default 200) to cap the list. An unknown slug answers `404`. ```bash curl "https://api.coritan.com/api/v1/client/containers/catalog/minecraft-paper/compose?limit=20" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` Place the order with `POST /api/v1/services/order`, as [Order a service](/docs/get-started/order-a-service/#with-the-api) describes, with the plan's `product_id` and `pricing_id`. For a server, send its settings in `config`: `location` : The location code, as the plan's locations list it. `name` : The server name, 2–60 characters. When you leave it out, we use the top-level `hostname`. `specialization_slug` : The software's `slug` from the catalogue. `software_version` : Optional. A `version_id` from the compose response. Leave it out to install the latest release. `runtime_template_slug` : Optional. The `recommended_runtime_slug` that goes with the version you chose. `variables` : Optional. Values for the fields the software asks for, keyed by variable name. The plan sets the server's resources, whatever `config` says about them. To buy add-ons in the same order, list them in `addons`, as [Add-ons in the order](/docs/get-started/order-a-service/#add-ons-in-the-order) describes. A server takes one floating IP, and only a `/32`. A DDoS Shield profile needs that floating IP in the same order. This example adds a dedicated address: ```bash curl -X POST https://api.coritan.com/api/v1/services/order \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "product_id": 12, "pricing_id": 34, "hostname": "survival-smp", "config": { "location": "fra", "name": "survival-smp", "specialization_slug": "minecraft-paper" }, "addons": [ {"product_id": 31} ] }' ``` When the response has `requires_payment` set to `true`, pay the invoice it names in `invoice_id` before setup starts. The errors you can act on: - `403` with `"error": "plan_not_sold_here"` and the message `This plan is not sold here.`: coritan.com does not sell the free plan, and `GET /api/v1/products/` leaves it out. [How free servers work](/docs/managed-containers/free-servers/) covers free servers you already have. - `422` `location is required (airport code, e.g. iad)`, or `Provide template_uuid, specialization_slug, recipe_uuid, runtime_template_slug, or image` when `config` names no software. - `422` `A server takes a single address. Choose the /32 floating IP.` or `A server takes one floating IP, so add at most one.` for a floating IP in `addons`. - `422` with a message ending `needs an address to protect. Add a floating IP to this order.` for a DDoS Shield profile without a floating IP. ## API - `GET /api/v1/client/containers/catalog`: Browse platform specializations available for deploy composition (https://www.coritan.com/docs/api/reference/client/catalog/#op-get-api-v1-client-containers-catalog) - `GET /api/v1/client/containers/catalog/{slug}/compose`: Get software versions + recommended runtimes for a specialization (https://www.coritan.com/docs/api/reference/client/catalog/#op-get-api-v1-client-containers-catalog-slug-compose) # Host a Minecraft server > Order a Minecraft server, install plugins, invite friends and keep the world safe, from start to finish. Source: https://www.coritan.com/docs/managed-containers/host-a-minecraft-server/ This tutorial takes you from an empty account to friends playing on your own Minecraft: Java Edition server. You order the server, start it, join it, add plugins or mods, make yourself an operator and set up snapshots that keep the world safe. Each section ends with something you can check before you go on. ## Before you begin - A Coritan account. [Create an account](/docs/get-started/create-an-account/) if you have none, then sign in. - A way to pay: a [payment method](/docs/billing/payment-methods/) or [credit](/docs/billing/add-credit/) on the account. - Minecraft: Java Edition on your computer. Bedrock players can join too once you turn on [Bedrock crossplay](/docs/managed-containers/software/bedrock-crossplay/). ## 1. Order the server 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and select **Order server**. The **Container Apps** order page opens, with its sections numbered down the page and the summary beside them. 2. Under **Plan**, choose the billing cycle at the top of the section, then a plan under **Size**. Each row shows the plan's price, vCPUs, memory and storage. A world with many players, plugins or mods needs more memory, and you can [change the plan](/docs/billing/change-plan/) later. 3. Under **Software**, select **Game servers**, then **Minecraft**, and choose the flavour: - Minecraft Paper runs plugins. - Minecraft Fabric, Minecraft Forge and Minecraft NeoForge run mods. - Minecraft Vanilla runs the game as Mojang releases it, without plugins or mods. 4. Under **Version**, leave **Latest** selected, or choose the version your players run. Players need the same game version as the server. 5. Under **Location**, choose the data centre closest to your players. 6. Under **Settings**, replace the suggested **Server name** with your own, such as `survival-smp`. The dashboard has no way to rename a server later. 7. Check the summary under **Your order**, then select the button under it. It reads **Deploy server** when your credit pays the first invoice, and **Place order and pay** when you pay on the next page. 8. If the order's page reads **Order placed, payment due**, pay under **Pay invoice**. We set the server up once it is paid. The order's page shows **Setting up**, and `survival-smp` appears on the **Container Apps** page with the status `Installing`. [Order a server](/docs/managed-containers/order-a-server/) describes every section of the order page, including add-ons such as a dedicated address, which players join on the default port without typing one. ## 2. Start the server 1. On the **Container Apps** page, open `survival-smp`. While the software installs, the **Console** tab shows **Console unavailable**. 2. The page does not update by itself when the install finishes. Reload it from time to time until the console appears. 3. Read the [Minecraft EULA](https://aka.ms/MinecraftEULA). The game runs only when the file `eula.txt` on the server says `eula=true`, and our install writes that line for you. The line says that you accept the EULA. 4. Select **Start** at the top of the page. We do not start a new server for you when its install finishes. The status badge at the top of the page goes from `Starting` to `Running`. The console prints a line that starts with `Done` when the world has loaded, and the **Live stats** card then shows **Players**, **Version** and **Online now**. ## 3. Join the server 1. At the top of the server page, select **Copy address**. The address is an IP address and a port, such as `203.0.113.10:10432`. 2. In Minecraft, select Multiplayer, then Add Server, and paste the address into Server Address. Select Done. 3. Select the server in the list and join it. 4. Send the same address to your friends. The IP address is shared with other servers, and the port tells them apart. Players must type the whole address, port included. For a name without a port, [create a join address](/docs/proxies/join-addresses/create-a-join-address/) under `coritan.gg`, such as `survival-smp.coritan.gg`. Leave **PROXY protocol** off when you create it: you cannot change it later, and a server that does not expect it refuses every player. [Edge Proxy](/docs/proxies/) explains join addresses and how to use a domain of your own. You are in the world, and the console shows `Alex joined the game`, with your Minecraft name in place of `Alex`. ## 4. Add plugins or mods Take a [snapshot](/docs/managed-containers/snapshots/) before you change what the server runs, so you can go back if something breaks. Section 6 shows how. To add a plugin to a Minecraft Paper server: 1. Open the server's **Software** tab and select **Marketplace**. 2. In the list before the search field, choose **Plugins**. Type a name, such as `LuckPerms`, and select **Search**. 3. Select **Install…** on the result. In **Version**, choose a build made for your server's software and Minecraft version. 4. Select **Dependencies**. The dashboard installs only the plugin you chose, so note each `Required` project that shows `Not installed`, and install it the same way afterwards. 5. Select **Install**. Wait until the job at the top of the tab shows `Succeeded`. 6. Select **Restart** at the top of the page. The server loads a new plugin only when it starts. A Paper server does not load mods. To run mods, switch the server to a mod loader first: 1. On the **Software** tab, the **Server software** panel opens first. Under **Choose the software to run**, select Minecraft Fabric, Minecraft Forge or Minecraft NeoForge. 2. Choose the Minecraft version the world runs now, or a newer one. Minecraft cannot load a world saved by a newer version. 3. Choose **Keep my files**, so the world stays. Plugins stay on the disk too, but a mod loader does not load them. 4. Select **Switch software**. The change stops the server. Wait until the job shows `Succeeded`. 5. Select **Start**, then install mods from **Marketplace** as above, with **Mods** chosen in place of **Plugins**. The addon is listed under **Installed from the marketplace** and on the **Installed addons** panel, and the console shows it loading when the server starts. [Install plugins, mods and modpacks](/docs/managed-containers/software/plugins-and-mods/) covers modpacks and datapacks. ## 5. Make yourself an operator An operator can run the game's admin commands, such as `/gamemode` and `/ban`, from inside the game. 1. Open the **Console** tab. 2. Type `op Alex`, with your Minecraft name in place of `Alex` and without a leading `/`. 3. Press Enter or select **Send**. To let only the players you choose join, send `whitelist add` followed by each player's name, yours included, and then `whitelist on`. The console shows `Made Alex a server operator`, and in the game you can now run `/gamemode creative`. ## 6. Keep the world safe Snapshots have replaced [backups](/docs/managed-containers/backups/) on servers. A snapshot is a full copy of the server's files, kept on your account so it outlives the server. Take one before any big change, and let a schedule take one every night. 1. Open the **Snapshots** tab and select **Take snapshot…**. 2. Check the **Name**, then select **Take snapshot**. 3. Open the **Schedules** tab and select **New schedule…**. 4. Enter the **Name** `Nightly snapshot`. Keep the **Cron expression** the form starts with, `0 4 * * *`, which runs at 04:00 every day in the **Time zone** you choose. Select **Create schedule**. 5. Select the schedule's name to open it, then select **Add task**. 6. Set **Action** to **Take a snapshot**, then select **Add task**. Snapshots use the snapshot storage that your plan includes. When it is full, the schedule deletes the oldest unlocked snapshots it took of this server to make room. It never deletes a snapshot you took yourself. [How snapshot storage works](/docs/snapshots/storage-allowance/) explains the allowance. Your snapshot appears on the **Snapshots** tab, and its status goes from `Pending` to `Creating`, then to `Completed`. The schedule shows **Active**, with its next run beside the timetable. ## Troubleshooting **Console unavailable** stays after a reload : The install is still running, or it failed. When the page says the install did not finish, see [the install failed](/docs/managed-containers/troubleshooting/#the-install-failed). The game cannot connect to the server : Check that the badge shows `Running` and that the address you typed has its port. [Players cannot connect](/docs/managed-containers/troubleshooting/#players-cannot-connect) lists the other causes. The game refuses the server because of its version : The player's game runs another Minecraft version. Pick that version in the Minecraft launcher, or change the server's version on the **Software** tab. A plugin or mod does not load : Install its `Required` dependencies, then restart the server. [Install plugins, mods and modpacks](/docs/managed-containers/software/plugins-and-mods/#troubleshooting) lists the install errors. Every player is refused through the join address : **PROXY protocol** is on and the server does not expect it. See [Troubleshoot proxies and join addresses](/docs/proxies/troubleshooting/). ## Where to go next - [Use your own domain for a join address](/docs/proxies/join-addresses/custom-domain/), such as `play.example.com`. - [Share the server with other users](/docs/managed-containers/users/), so a friend can help you run it. - [Schedule server tasks](/docs/managed-containers/schedules/), such as a nightly restart that warns players first. - [Let Bedrock players join a Java server](/docs/managed-containers/software/bedrock-crossplay/). - [Import a server from another host](/docs/managed-containers/import/) to bring a world you already have. - [Troubleshoot servers](/docs/managed-containers/troubleshooting/) and [Server limits and rate limits](/docs/managed-containers/limits/) cover everything else. # Use the console and power controls > Start, stop, restart and kill a server, send console commands and read its live output and resource usage. Source: https://www.coritan.com/docs/managed-containers/console/ In the dashboard: - /dashboard/servers/…/console: https://www.coritan.com/dashboard/servers The **Console** tab is where you watch a server run. It streams the server's output as it happens, takes the commands you type, and shows live CPU, memory, disk and network use. The power buttons sit at the top of the server page, so you can start or stop the server from any tab. ## Before you begin - The server must have finished installing. The console is not available while the server's status is `installing`, `install_failed`, `transferring` or `suspended`. - On a server someone shared with you, the owner must have given you the matching permissions: Console (or Command, which grants the same) to open the console and send commands, and Power to start, stop, restart and kill. [Share a server with other users](/docs/managed-containers/users/) lists them. - A free server that is asleep starts with **Wake server** instead of **Start**. See [How free servers work](/docs/managed-containers/free-servers/). ## Start, stop or restart the server 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the server. 2. At the top of the page, select the power action you need: - **Start** starts a stopped server. - **Restart** stops a running server and starts it again. - **Stop** shuts a running server down cleanly, so it saves its data first. The page only offers the actions that fit the server's state: **Start** while it is stopped, **Restart** and **Stop** while it runs. ## Kill a server that does not stop Kill ends the server process at once, without letting it save. Use it only when **Stop** does not work. 1. Open the server, then open **More power actions** (the button with three dots) at the top of the page. 2. Select **Kill process…**. 3. Read the warning and select **Kill process**. > [!WARNING] > Kill does not wait for the server to save. You lose anything the server has not yet written to disk, such as the last minutes of a game world. ## Send a command 1. Open the server, then the **Console** tab. 2. Type the command in the box under the output, for example `say Server restarts in 5 minutes`. 3. Press Enter or select **Send**. The server's reply appears in the output. While the console is disconnected, the box says so and the command goes through the API instead. The output then shows `>` followed by your command. ## Read the output and live stats - The status next to the server's name says whether the output is live: **Connected**, **Connecting**, **Reconnecting**, **Paused in background** or **Disconnected**. Select **Reconnect** whenever it is not connected. - When you leave the tab in the background, the console pauses and reconnects when you come back. - The output keeps the last 2,000 lines. - The **Live stats** card shows **CPU**, **Memory** and **Disk** against the plan's limits, **Network in**, **Network out** and **Uptime**. - For Minecraft servers, the card and the bar above the output also show **Players**, **Version** and **Online now** when the game answers the server list ping. ## Result The status badge at the top of the page changes as the server starts or stops, and a message confirms each action, such as `Starting the server.` The output shows the server's own messages as it boots, and commands you send appear with their replies. ## Troubleshooting **Console unavailable** : The server is installing, being moved, suspended, or its install failed. The page says which. The console opens again when the server is ready. For a failed install, see [Troubleshoot servers](/docs/managed-containers/troubleshooting/). `Access denied. You need console permission.` : The owner has not given you the Console permission on this server. Ask them to add it on the **Users** tab. `Connection limit reached. Close other console sessions first.` : Your account has 8 consoles open on this server, or 32 across all servers. Close console tabs you are not using. `Session expired. Please sign in again.` : Your session ended. Sign in again, then reopen the console. `Cannot reach the host this server runs on` : The console could not reach the machine that runs the server. It keeps trying. If it stays disconnected, [contact support](https://www.coritan.com/dashboard/support). `Cannot send power actions while server is suspended` : A suspended server cannot start. Select **Open Billing** on the banner to check the plan's billing status, or open a support ticket. `Free servers must be started via the wake endpoint, not a direct power start/restart` : This is a free server. Start it with **Wake server** on the **Console** tab. **This server is locked** : We locked the server during an abuse review. It can be stopped but not started. Select **Open review ticket** to reply to the ticket. `world_optimization_running` : We are removing unused chunks from the world, and the server cannot start until that finishes. Try again after the number of seconds in `retry_after_seconds`. `rate_limited` : You sent more than 24 power actions to this server in 60 seconds. Wait for the number of seconds in the `Retry-After` header. ## Related - [Server settings](/docs/managed-containers/settings/) shows the startup command the server runs. - [Schedule server tasks](/docs/managed-containers/schedules/) sends commands and power actions on a timer. - [Server limits and rate limits](/docs/managed-containers/limits/) lists every limit on this page. ## With the API Send a power action with `POST /api/v1/client/servers/{uuid}/power`. The body's `signal` is `start`, `stop`, `restart` or `kill`. ```bash curl -X POST https://api.coritan.com/api/v1/client/servers/$SERVER/power \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"signal": "restart"}' ``` The response is `{"message": "Power action 'restart' sent"}`. It confirms that the signal was sent, so read the server's state afterwards to see the result. Send a console command with `POST /api/v1/client/servers/{uuid}/command`. The command goes in the `command` query parameter, URL-encoded, at most 1,000 characters. ```bash curl -X POST "https://api.coritan.com/api/v1/client/servers/$SERVER/command?command=say%20Hello" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` `GET /api/v1/client/servers/{uuid}/resources` returns the server's `state` (such as `running`, `starting`, `stopping` or `offline`), `is_suspended`, and `utilization`, which holds `cpu_absolute`, `memory_bytes`, `memory_limit_bytes`, `disk_bytes`, `network.rx_bytes`, `network.tx_bytes` and `uptime` in milliseconds. `GET /api/v1/client/servers/{uuid}/status-ping` asks a Minecraft server who is online. When `reachable` is `true`, the response carries `players_online`, `players_max`, `sample` (some player names), `version`, `motd` and `latency_ms`. When the game does not answer (stopped, still booting, or not Minecraft), `reachable` is `false` and the other fields are empty. To stream the output, open the console socket with your access token in the `token` query parameter. `GET /api/v1/client/servers/{uuid}/websocket` returns the socket's address as `socket`. The socket signs you in with your access token, so you do not need the `token` field of that response. ```text wss://api.coritan.com/ws/console/?token= ``` Messages are JSON objects with an `event` and `args`. Send `{"event": "send logs", "args": []}` for the recent output, `{"event": "send command", "args": ["say Hello"]}` to run a command, and `{"event": "set state", "args": ["restart"]}` for a power action. The socket sends `console output`, `status` and `stats` events as the server runs. ## API - `POST /api/v1/client/servers/{uuid}/power`: Execute power action (start, stop, restart, kill) (https://www.coritan.com/docs/api/reference/client/container-apps/servers/#op-post-api-v1-client-servers-uuid-power) - `POST /api/v1/client/servers/{uuid}/command`: Execute a command on the server (https://www.coritan.com/docs/api/reference/client/container-apps/servers/#op-post-api-v1-client-servers-uuid-command) - `GET /api/v1/client/servers/{uuid}/websocket`: Get WebSocket token and endpoint for console access (https://www.coritan.com/docs/api/reference/client/container-apps/servers/#op-get-api-v1-client-servers-uuid-websocket) - `GET /api/v1/client/servers/{uuid}/resources`: Get server resource usage and limits (https://www.coritan.com/docs/api/reference/client/container-apps/servers/#op-get-api-v1-client-servers-uuid-resources) - `GET /api/v1/client/servers/{uuid}/status-ping`: Ask the game itself who is online (https://www.coritan.com/docs/api/reference/client/container-apps/servers/#op-get-api-v1-client-servers-uuid-status-ping) # Manage server files > Browse, edit, upload, download, archive and extract your server's files in the dashboard file manager. Source: https://www.coritan.com/docs/managed-containers/files/ In the dashboard: - /dashboard/servers/…/files: https://www.coritan.com/dashboard/servers The **Files** tab is a file manager for your server's disk. Use it to change configuration files, add plugins or worlds and tidy up, without installing anything on your computer. For whole folders or files over 100 MB, [connect with SFTP](/docs/managed-containers/sftp/) instead. ## Before you begin - On a server someone shared with you, the owner must have given you the Files permission. It covers everything on this page, and SFTP. - You cannot change files while the server is installing or being moved, and you cannot open them at all during a move. - Most games read their settings when they start. Restart the server after you edit a configuration file, and stop it before you replace its world, so it does not save over your change. ## Find your way around 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the server, then the **Files** tab. 2. Select a folder's name to open it. The path above the list shows where you are. Select any folder in the path, or the home icon at its start, to go back up. The list shows each entry's **Name**, **Size**, **Modified** date and **Mode**, its permissions such as `-rw-r--r--`. Folders come first. A **Symlink** badge marks a link to another file. Select **Refresh** to read the folder again. The folder is part of the page address, so you can bookmark it or send the link to someone who shares the server. ## Edit a file 1. Select the file's name, or open the menu at the end of its row and select **Edit…**. 2. Make your change. Clear **Wrap long lines** to keep each line on one row. 3. Select **Save file**, or press Ctrl+S (⌘+S on a Mac). The editor opens text files up to 2 MB, such as `.yml`, `.json`, `.properties` and `.txt` files. Selecting the name of any other file downloads it. An **Unsaved changes** badge shows until you save, and closing the editor with unsaved changes asks you to confirm with **Discard changes**. The editor will not save over a change it has not shown you. If the file changed on the server after you opened it, the save stops and asks you to reopen the file. ## Create a file or folder 1. Open the folder to create it in. 2. Select **New**, then **New file…** or **New folder…**. 3. Enter the **File name** or **Folder name**, then select **Create file** or **Create folder**. A new file is empty, so select its name to add content. The dashboard does not create a file over one that already exists. A name cannot contain `/` and can be up to 255 characters. ## Upload files 1. Open the folder to upload into. 2. Select **Upload** and choose one or more files, or drag files from your computer onto the list. The **Transfers** panel shows each file's progress, and **Cancel** stops one. Each file can be up to 100 MB. An upload replaces a file of the same name without asking. The dashboard uploads files, not folders. To upload a folder, compress it on your computer, upload the archive and [extract it](#extract-an-archive), or use SFTP. ## Download files - Open the menu at the end of a file's row and select **Download**. Selecting the name of a file the editor cannot open downloads it too. - To download a folder, [compress it](#compress-files-into-an-archive) and download the archive. ## Download a file from a URL Fetch a file straight onto the server, such as a plugin from its release page, without saving it on your computer first. 1. Open the folder to save the file in. 2. Select **New**, then **Download from URL…**. 3. Enter the **URL**, an `http` or `https` address. 4. Optionally, enter a name in **Save as**. Leave it empty to keep the name from the URL. 5. Select **Download to server**. The server downloads the file after you confirm, so a large file can take a while to appear. Select **Refresh** to check. Addresses on private networks are refused. ## Rename, move or duplicate 1. Open the menu at the end of the row and select **Rename**. 2. Type the new name and press Enter, or press Esc to cancel. To move a file or folder, type a path relative to the current folder as its new name: `../Essentials.jar` moves it up one folder, and `old/Essentials.jar` moves it into the `old` folder. To copy a file, select **Duplicate** in its menu. The copy appears in the same folder with `copy` in its name. Folders cannot be duplicated. ## Compress files into an archive 1. Tick the box beside each file and folder to include. The number you picked shows above the list. 2. Select **Compress…**, then **Compress**. The archive is a `.tar.gz` file saved in the server root folder, whichever folder you are in, and the server chooses its name. The **Archive name** you enter is not used. Open the root folder to find the new archive, then rename it if you want. ## Extract an archive Open the menu at the end of the archive's row and select **Extract here**. The files go into the folder that holds the archive, and they replace files with the same names. **Extract here** appears for `.zip`, `.tar`, `.tar.gz`, `.tgz`, `.tar.bz2`, `.tbz2`, `.tar.xz`, `.txz`, `.rar`, `.7z`, `.gz`, `.bz2` and `.xz` files. ## Change permissions 1. Open the menu at the end of the row and select **Permissions…**. 2. Tick **Read**, **Write** and **Execute** for **Owner**, **Group** and **Others**, or type three digits in **Octal**, such as `644` for a file or `755` for a folder. 3. Select **Apply permissions**. ## Delete files and folders 1. Open the menu at the end of the row and select **Delete file…** or **Delete folder…**. To delete several at once, tick their boxes and select **Delete…**. 2. Confirm with **Delete file**, **Delete folder**, or the button that gives the number of items. > [!CAUTION] > Deleting a folder deletes everything in it, and you cannot undo it. The only way back is a [snapshot](/docs/managed-containers/snapshots/) taken before you deleted the files. ## Result Each action confirms with a message, such as `File saved.` or `Folder created.`, and the list shows the change. Uploads, downloads, archives and downloads from a URL report in the **Transfers** panel, which marks each one **Done** or says why it failed. ## Troubleshooting `The file changed on the server since you opened it. Reopen it and save again.` : The server, another person or an SFTP client changed the file after you opened it. Copy your change, close the editor, open the file again and make the change once more. `File changes are paused while the server is installing.` : The server is installing or being moved. Try again when it has finished. `File exceeds maximum upload size of 100 MB` : Upload the file [over SFTP](/docs/managed-containers/sftp/), or use **Download from URL…** if the file is online. **Could not list this folder** : The dashboard could not read the folder, and the message under it gives the reason. Try again, and if the server is being moved, wait until it finishes. `Free servers cannot modify or upload core server executables. …` : On a free server you cannot upload, overwrite or rename the file the server runs, such as `server.jar`, or extract an archive that contains it. Change what the server runs on the **Software** tab. See [Change the server software](/docs/managed-containers/software/). `Free servers can extract archives of up to 1 GB through the panel, …` : A free server checks every archive before it extracts it, and cannot check one over 1 GB. Extract the archive on your computer and upload the files. `URL targets a private or reserved network` : **Download from URL…** only fetches from public addresses. `Too many requests for this action. Please wait and try again.` : You compressed and extracted more than 24 times, or downloaded from a URL more than 24 times, on this server in 60 seconds. Wait a minute and try again. **Edit…** is missing from the menu : The editor opens text files up to 2 MB. Download a larger file, edit it on your computer and upload it again, or edit it over SFTP. ## Related - [Connect to a server with SFTP](/docs/managed-containers/sftp/) for large files and whole folders. - [Take a server snapshot](/docs/managed-containers/snapshots/) before a big change, so you can go back. - [Server limits and rate limits](/docs/managed-containers/limits/) lists every file limit. ## With the API Every file route is under `/api/v1/client/servers/{uuid}/files/`. Paths start at the server root, such as `/plugins/config.yml`. A path that climbs above the root with `..` is refused, and a request that names several files takes up to 200. List a folder with `GET files/list`: ```bash curl "https://api.coritan.com/api/v1/client/servers/$SERVER/files/list?directory=/plugins" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` The response is `{"files": [...]}`. Each entry has `name`, `size` in bytes, `mode` (such as `-rw-r--r--`), `mode_bits` (such as `644`), `file`, `directory` and `symlink` (each `true` or `false`), `mime`, `created` and `modified`. `GET files/contents?path=/server.properties` returns the file's text as `content` and its SHA-256 as `hash`. `POST files/write` takes `path`, `content` (up to 8 MB) and `expected_hash`: - Send the `hash` you read, and the write is refused if the file has changed since. - Send `""`, and the write only creates a new file. - Leave `expected_hash` out to overwrite the file whatever it holds. ```bash curl -X POST https://api.coritan.com/api/v1/client/servers/$SERVER/files/write \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"path": "/server.properties", "content": "motd=Welcome to survival-smp\n", "expected_hash": ""}' ``` A refused write answers `409` with `code` set to `file_conflict`, the file's `current_hash`, and `missing` set to `true` when the file is gone. A successful write returns the new `hash`. Upload with `POST files/upload`, a multipart form with one `files` field per file, each up to 100 MB. The response lists what landed in `files` and what did not, with the reason, in `failed`. ```bash curl -X POST "https://api.coritan.com/api/v1/client/servers/$SERVER/files/upload?directory=/plugins" \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -F "files=@EssentialsX.jar" ``` `GET files/download?path=/logs/latest.log` streams the file as an attachment. With curl, add `-OJ` to save it under its own name. The other routes take a JSON body: | Route | Example body | What it does | |---|---|---| | `POST files/mkdir` | `{"root": "/", "name": "plugins"}` | Creates the folder `name` in `root`. | | `POST files/copy` | `{"location": "/plugins/config.yml"}` | Copies a file into the same folder. | | `POST files/rename` | `{"old_path": "/notes.txt", "new_path": "/old/notes.txt"}` | Renames or moves a file or folder. | | `POST files/delete` | `{"path": "/logs"}` | Deletes one file, or one folder with everything in it. | | `POST files/chmod` | `{"root": "/plugins", "files": [{"file": "config.yml", "mode": "644"}]}` | Sets permissions. `mode` is three octal digits, or four starting with `0`. | | `POST files/compress` | `{"paths": ["/world", "/server.properties"], "archive_name": "world"}` | Makes a `.tar.gz` in `/`. `archive_name` is required but not used; the response's `archive` describes the new file. | | `POST files/decompress` | `{"archive_path": "/uploads/world.zip", "destination": "/uploads"}` | Extracts into the folder that holds the archive. `destination` counts only when `archive_path` is a bare file name. | | `POST files/pull` | `{"url": "https://example.com/plugin.jar", "destination_path": "/plugins", "filename": "plugin.jar"}` | Starts a download onto the server and returns its `identifier`. `filename` is optional. | While the server is installing or being moved, changes answer `409` with `reason` set to `server_busy`, the server's `status` and a `message`. On a server shared with you, each route needs one permission: `file.read` to list, `file.read-content` to read or download, `file.create` to create, upload, copy or pull, `file.update` to write, rename or change permissions, `file.delete` to delete, and `file.archive` to compress or extract. The Files permission in the dashboard grants all of them, and `file.sftp` as well. ## API - `POST /api/v1/client/servers/{uuid}/files/chmod`: Set the mode of files under root: [{"file": name, "mode": "0644"}] (https://www.coritan.com/docs/api/reference/client/container-apps/servers-files/#op-post-api-v1-client-servers-uuid-files-chmod) - `POST /api/v1/client/servers/{uuid}/files/compress`: Create a compressed archive of files (https://www.coritan.com/docs/api/reference/client/container-apps/servers-files/#op-post-api-v1-client-servers-uuid-files-compress) - `GET /api/v1/client/servers/{uuid}/files/contents`: Get contents of a file (https://www.coritan.com/docs/api/reference/client/container-apps/servers-files/#op-get-api-v1-client-servers-uuid-files-contents) - `POST /api/v1/client/servers/{uuid}/files/copy`: Duplicate a file next to itself; Wings picks the name copy suffix (https://www.coritan.com/docs/api/reference/client/container-apps/servers-files/#op-post-api-v1-client-servers-uuid-files-copy) - `POST /api/v1/client/servers/{uuid}/files/decompress`: Extract an archive on the server (https://www.coritan.com/docs/api/reference/client/container-apps/servers-files/#op-post-api-v1-client-servers-uuid-files-decompress) - `POST /api/v1/client/servers/{uuid}/files/delete`: Delete a file (https://www.coritan.com/docs/api/reference/client/container-apps/servers-files/#op-post-api-v1-client-servers-uuid-files-delete) - `GET /api/v1/client/servers/{uuid}/files/download`: Stream one file from Wings through the platform, as an attachment (https://www.coritan.com/docs/api/reference/client/container-apps/servers-files/#op-get-api-v1-client-servers-uuid-files-download) - `GET /api/v1/client/servers/{uuid}/files/list`: List files in a server directory (https://www.coritan.com/docs/api/reference/client/container-apps/servers-files/#op-get-api-v1-client-servers-uuid-files-list) - `POST /api/v1/client/servers/{uuid}/files/mkdir`: Create a folder name under root (https://www.coritan.com/docs/api/reference/client/container-apps/servers-files/#op-post-api-v1-client-servers-uuid-files-mkdir) - `POST /api/v1/client/servers/{uuid}/files/pull`: Download and save a file from a URL (https://www.coritan.com/docs/api/reference/client/container-apps/servers-files/#op-post-api-v1-client-servers-uuid-files-pull) - `POST /api/v1/client/servers/{uuid}/files/rename`: Rename a file (https://www.coritan.com/docs/api/reference/client/container-apps/servers-files/#op-post-api-v1-client-servers-uuid-files-rename) - `POST /api/v1/client/servers/{uuid}/files/upload`: Multipart upload proxied to Wings, so the browser never talks to the node (https://www.coritan.com/docs/api/reference/client/container-apps/servers-files/#op-post-api-v1-client-servers-uuid-files-upload) - `POST /api/v1/client/servers/{uuid}/files/write`: Write content to a file (https://www.coritan.com/docs/api/reference/client/container-apps/servers-files/#op-post-api-v1-client-servers-uuid-files-write) # Connect to a server with SFTP > Find your server's SFTP address and username, then connect with FileZilla, WinSCP or the sftp command. Source: https://www.coritan.com/docs/managed-containers/sftp/ In the dashboard: - /dashboard/servers/…/files: https://www.coritan.com/dashboard/servers SFTP lets a desktop client such as FileZilla, WinSCP or Cyberduck, or the `sftp` command, read and write your server's files. Use it for whole folders and for files over 100 MB, which the [dashboard file manager](/docs/managed-containers/files/) cannot upload. ## Before you begin - You need your Coritan account password. SFTP signs in with the same password as the dashboard. - On a server someone shared with you, the owner must have given you the Files permission. You sign in with your own email address and password. - The server must not be suspended. - SFTP accepts a password only. It does not accept SSH keys, and it does not ask for a two-factor code. ## Find the connection details 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the server, then the **Files** tab. 2. On the **SFTP connection** card at the top, select **Show details**. 3. Copy each value with **Copy host**, **Copy port** and **Copy username**. **Host** : The address of the machine your server runs on. It is not always the address players join. **Port** : `2022`. **Username** : Your account email address, a full stop, then the first eight characters of the server's ID, for example `alex@example.com.1a2b3c4d`. Each server has its own username. **Password** : Your account password. ## Connect with a desktop client 1. In your SFTP client, start a new connection and choose the SFTP protocol. FTP and FTPS do not work. 2. Enter the host, port `2022`, username and password from the dashboard. 3. Connect. The first time, the client asks whether to trust the server's host key. Accept it. ## Connect from the command line Pass the username with `-o User=`, because it contains an `@`: ```bash sftp -P 2022 -o User=alex@example.com.1a2b3c4d ``` Enter your account password when asked. Use `put` to upload, `get` to download, and `put -r` or `get -r` for a folder. ## Result Your client shows the server's files, starting at the server root, the same files the **Files** tab shows. Changes you make over SFTP show on the **Files** tab straight away. Restart the server after you change its configuration. ## Troubleshooting The client says the password or credentials are wrong : SFTP gives the same answer for every refused sign-in, so check each of these. Copy the username from the dashboard: it must hold your full email address, with the `@`, then a full stop and the eight characters. Use your current account password. On a shared server, check that the owner gave you the Files permission. A suspended server, or one we locked during an abuse review, refuses every sign-in, and the server page shows a banner when either applies. The username in the dashboard has no `@` in it : The page could not load the connection details and shows a guess instead. Reload the page and copy the username again. The client runs out of attempts before it asks for the password : Your client offers every SSH key it has first, and each one counts as a failed attempt. Turn off key sign-in for this connection. With the `sftp` command, add `-o PreferredAuthentications=password`. The connection times out or is refused : Check that the port is `2022`, not `22`, and that the protocol is SFTP. A firewall on your network may block port 2022. ## Related - [Manage server files](/docs/managed-containers/files/) in the dashboard. - [Share a server with other users](/docs/managed-containers/users/) explains the Files permission. - [Change or reset your password](/docs/account/password/) changes the password SFTP uses too. ## With the API `GET /api/v1/client/servers/{uuid}/sftp` returns the connection details for your account on that server. It never returns a password, because the password is your account password. ```bash curl https://api.coritan.com/api/v1/client/servers/$SERVER/sftp \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json {"host": "", "port": 2022, "username": "alex@example.com.1a2b3c4d"} ``` On a server shared with you, the route needs the `file.sftp` permission, which the Files permission in the dashboard includes. ## API - `GET /api/v1/client/servers/{uuid}/sftp`: Connection details for this server's SFTP login (https://www.coritan.com/docs/api/reference/client/container-apps/servers/#op-get-api-v1-client-servers-uuid-sftp) # Create and manage server databases > Create a MySQL-compatible database for your server, read its credentials, rotate its password and delete it. Source: https://www.coritan.com/docs/managed-containers/databases/ In the dashboard: - /dashboard/servers/…/databases: https://www.coritan.com/dashboard/servers Many plugins and mods keep their data in a MySQL database: permissions, economy balances, player statistics. The **Databases** tab creates MariaDB databases for your server, which work with any plugin that asks for MySQL. We create each one on a shared MariaDB host in the server's region. ## Before you begin - Your plan sets how many databases the server can have. The **Databases** card shows how many slots you have used, and [Server settings](/docs/managed-containers/settings/) shows the plan's **Database slots**. - Free servers do not include databases. See [How free servers work](/docs/managed-containers/free-servers/). - On a server someone shared with you, the owner must have given you the Databases permission. ## Create a database 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the server, then the **Databases** tab. 2. Select **New database…**. 3. Enter a **Name suffix** that says what the database is for, such as `stats` or `luckperms`. Use lowercase letters, digits and underscores, up to 48 characters. 4. Leave **Database host** as it is. We always use the host in the server's region, whatever it says. 5. Select **Create database**. The database's name is `srv_`, your server's number, an underscore and your suffix, such as `srv_1234_stats`. We create a user for it with a random 24-character password. ## Connect a plugin to it 1. Select the database's name, or open the menu at the end of its row and select **Show credentials**. 2. Copy the values into your plugin's configuration file. Select **Reveal** to see the password, or **Copy all** to copy every value at once. 3. Restart the server so the plugin connects. **Host** : The address of the database server. Use this value rather than the **Host** column in the list. **Port** : Usually `3306`. **Database** : The database's name, such as `srv_1234_stats`. **Username** and **Password** : The database user. You can show the password again at any time. **Connection URI** : The same details in one `mysql://` address, for plugins that ask for a URL. It includes the password. **JDBC** : The same details as a `jdbc:mysql://` address, for Java plugins that ask for one. It includes the password. ## Rotate the password Rotate the password when you think someone else has seen it. 1. Open the menu at the end of the database's row and select **Rotate password…**. 2. Select **Rotate password**. 3. Copy the new password from the window that opens, and put it in every plugin that uses the database. > [!IMPORTANT] > The old password stops working straight away. Plugins that use it cannot reach the database until you give them the new one and restart the server. ## Delete a database 1. Open the menu at the end of the database's row and select **Delete database…**. 2. Type the database's name to confirm, then select **Delete database**. We delete the database and every table in it, and you cannot undo it. A [snapshot](/docs/managed-containers/snapshots/) holds the server's files, not its databases, so export anything you want to keep first with a plugin or a MySQL client. ## Result A new database shows in the list with its user under the name, and its credentials open straight away. The **Status** column shows each database's state, and a message confirms each action, such as `Database created.` or `Database deleted.` ## Troubleshooting The status shows a failure and **Try again** : We could not finish creating, rotating or deleting the database. The error shows under its name. We keep trying on our own, and **Try again** tries at once. If it keeps failing, [contact support](https://www.coritan.com/dashboard/support) with the error. **New database…** is greyed out : Every database slot is in use. Delete a database you no longer need, or move to a plan with more slots. See [Change a service's plan](/docs/billing/change-plan/). `Database limit reached (3/3)` : The same limit, reached through the API or another tab. The numbers are your database count and your plan's limit. `A database named srv_1234_stats already exists on this host` : The server already has a database with that suffix. Choose another suffix. `The database host is unreachable. We will keep trying; you can also retry from the list.` : The database host did not answer. We keep trying, and the database finishes when the host is back. Select **Try again** to retry sooner. `No database host on this server's node` : Databases are not available where your server runs. [Contact support](https://www.coritan.com/dashboard/support). `Retry the failed create before rotating the password` : The database was never created. Select **Try again** first, then rotate the password. `Too many requests for this action. Please wait and try again.` : You created more than 24 databases on this server in 60 seconds. Wait a minute and try again. ## Related - [Manage server files](/docs/managed-containers/files/) to edit the plugin's configuration file. - [Share a server with other users](/docs/managed-containers/users/) explains the Databases permission. - [Server limits and rate limits](/docs/managed-containers/limits/) ## With the API Create a database with `POST /api/v1/client/servers/{uuid}/databases`. The body takes `name_suffix` (lowercase letters, digits and `_`, up to 48 characters) and `remote`, a MariaDB host pattern for where the user may connect from. `remote` defaults to `%`, which allows any address. We choose the database host, so any `database_host_id` you send is ignored. ```bash curl -X POST https://api.coritan.com/api/v1/client/servers/$SERVER/databases \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: create-stats-db-1" \ -d '{"name_suffix": "stats"}' ``` The response (`201`) carries `id`, `name`, `username`, `password`, `host`, `port`, `uri`, `jdbc` and `status`. With an `Idempotency-Key` header, sending the same request again returns the same database instead of a second one. See [Idempotency](/docs/api/idempotency/). The other routes use the database's `id`: | Route | What it does | |---|---| | `GET /api/v1/client/servers/{uuid}/databases` | Lists the server's databases, without passwords. | | `GET /api/v1/client/servers/{uuid}/databases/{db_id}/credentials` | Returns one database with its `password`, `uri` and `jdbc`. | | `POST /api/v1/client/servers/{uuid}/databases/{db_id}/rotate-password` | Sets a new password and returns it. | | `POST /api/v1/client/servers/{uuid}/databases/{db_id}/retry` | Finishes a create, rotation or deletion that failed. | | `DELETE /api/v1/client/servers/{uuid}/databases/{db_id}` | Deletes the database and everything in it. | Each database has a `status`: `ready`, `provisioning` while we create it, `rotating`, `deleting`, or `failed`. `busy` is `true` during the three in-between states, and `last_error` holds the reason for a failure. Errors carry an `error` code and a `message`: `database_limit_reached`, `database_name_taken`, `database_busy` and `database_not_ready` answer `409`, and `database_host_unreachable` answers `503`. On a server shared with you, listing needs `database.read`, creating and retrying need `database.create`, rotating needs `database.update`, reading the password needs `database.view_password` and deleting needs `database.delete`. The Databases permission in the dashboard grants all five. ## API - `GET /api/v1/client/servers/{uuid}/databases`: List databases for a server (https://www.coritan.com/docs/api/reference/client/container-apps/servers-databases/#op-get-api-v1-client-servers-uuid-databases) - `POST /api/v1/client/servers/{uuid}/databases`: Create a new database (https://www.coritan.com/docs/api/reference/client/container-apps/servers-databases/#op-post-api-v1-client-servers-uuid-databases) - `DELETE /api/v1/client/servers/{uuid}/databases/{db_id}`: Delete a database (https://www.coritan.com/docs/api/reference/client/container-apps/servers-databases/#op-delete-api-v1-client-servers-uuid-databases-db-id) - `GET /api/v1/client/servers/{uuid}/databases/{db_id}/credentials`: Reveal the stored password for a database on this server (https://www.coritan.com/docs/api/reference/client/container-apps/servers-databases/#op-get-api-v1-client-servers-uuid-databases-db-id-credentials) - `POST /api/v1/client/servers/{uuid}/databases/{db_id}/retry`: Retry a failed or stuck database operation (https://www.coritan.com/docs/api/reference/client/container-apps/servers-databases/#op-post-api-v1-client-servers-uuid-databases-db-id-retry) - `POST /api/v1/client/servers/{uuid}/databases/{db_id}/rotate-password`: Rotate database password (https://www.coritan.com/docs/api/reference/client/container-apps/servers-databases/#op-post-api-v1-client-servers-uuid-databases-db-id-rotate-password) # Manage server backups > List, restore, download, lock and delete your server's backups, and learn when snapshots take their place. Source: https://www.coritan.com/docs/managed-containers/backups/ In the dashboard: - /dashboard/servers/…/backups: https://www.coritan.com/dashboard/servers Snapshots have replaced backups. You cannot create a new backup, so [take a snapshot](/docs/managed-containers/snapshots/) instead: it saves the same files, and you can restore it onto a different server too. The backups a server already has keep working. The **Backups** tab lists them, and from it you can restore, download, lock and delete them. ## Before you begin - Only servers that took backups before snapshots replaced them have any. Free servers never had backups. - On a server someone shared with you, the owner must have given you the Backups permission. ## Restore a backup 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the server, then the **Backups** tab. 2. Open the menu at the end of the backup's row and select **Restore…**. Only a finished backup offers it. 3. Type the backup's name to confirm, then select **Restore backup**. We stop the server, delete its current files and put the backup's files in their place. You lose every change made since the backup. A backup holds the server's files only, so its [databases](/docs/managed-containers/databases/) stay as they are. ## Download a backup Open the menu at the end of the backup's row and select **Download**. The download opens in a new browser tab. The file is a `.tar.gz` archive named after the backup's ID, and the link works for 10 minutes. ## Lock or unlock a backup Open the menu at the end of the backup's row and select **Lock**. A **Locked** badge shows beside its name, and nobody can delete it until someone selects **Unlock**. ## Delete a backup 1. Open the menu at the end of the backup's row and select **Delete backup…**. It is greyed out while the backup is locked. 2. Type the backup's name to confirm, then select **Delete backup**. We delete the archive from storage, and you cannot undo it. ## Result A message confirms each action, such as `Restore started.`, `Backup locked.` or `Backup deleted.` The **Status** column shows each backup's state, and a failed backup shows its error under its name. ## Troubleshooting `Backups have been replaced by snapshots. …` : **New backup…** still shows on the tab, but it cannot create a backup. [Take a snapshot](/docs/managed-containers/snapshots/) instead. `A backup or restore is already in progress on this server; wait for it to finish before you restore.` : Wait until the other restore finishes, then try again. `Only a finished backup can be restored` : The backup failed or never finished. Restore another backup, or a snapshot. `Cannot restore while the server is installing` : Wait until the server has finished installing or being moved, then try again. `This backup failed, so there is nothing to download` : A failed backup has no archive. Download another backup. `Cannot delete locked backup` : Select **Unlock** first, then delete it. `Too many requests for this action. Please wait and try again.` : You started more than 8 restores on this server in 5 minutes. Wait a few minutes and try again. ## Related - [Take a server snapshot](/docs/managed-containers/snapshots/) - [Snapshots and backups compared](/docs/snapshots/snapshots-and-backups/) - [Restore a snapshot onto a server](/docs/snapshots/restore-a-snapshot/) ## With the API `GET /api/v1/client/servers/{uuid}/backups` lists the server's backups. Each has `uuid`, `name`, `status`, `size_bytes`, `is_locked`, `ignored_files` and `created_at`. `status` is `completed` for a backup you can restore or download, `failed`, or `pending`, `creating`, `restoring` or `deleting` while work is under way. `POST /api/v1/client/servers/{uuid}/backups` answers `410` with `reason` set to `replaced_by_snapshots` and `replacement` set to `snapshots`. Create a snapshot with `POST /api/v1/client/servers/{uuid}/snapshots` instead. Restore a backup: ```bash curl -X POST https://api.coritan.com/api/v1/client/servers/$SERVER/backups/$BACKUP/restore \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` The response is `{"message": "Restore initiated", "status": "restoring"}`. `GET /api/v1/client/servers/{uuid}/backups/{backup_uuid}/download` returns a signed `url` to fetch the archive from, valid for `expires_in` seconds (600), with the backup's `name`, its size in `bytes`, and a `filename`. `POST /api/v1/client/servers/{uuid}/backups/{backup_uuid}/lock` locks an unlocked backup or unlocks a locked one, and returns the backup with its new `is_locked`. `DELETE /api/v1/client/servers/{uuid}/backups/{backup_uuid}` deletes an unlocked backup. On a server shared with you, listing needs `backup.read`, restoring needs `backup.restore`, downloading needs `backup.download`, and locking or deleting needs `backup.delete`. The Backups permission in the dashboard grants all of them. ## API - `GET /api/v1/client/servers/{uuid}/backups`: List backups for a server (https://www.coritan.com/docs/api/reference/client/container-apps/servers-backups/#op-get-api-v1-client-servers-uuid-backups) - `POST /api/v1/client/servers/{uuid}/backups`: Create a new backup (https://www.coritan.com/docs/api/reference/client/container-apps/servers-backups/#op-post-api-v1-client-servers-uuid-backups) - `DELETE /api/v1/client/servers/{uuid}/backups/{backup_uuid}`: Delete a backup (https://www.coritan.com/docs/api/reference/client/container-apps/servers-backups/#op-delete-api-v1-client-servers-uuid-backups-backup-uuid) - `GET /api/v1/client/servers/{uuid}/backups/{backup_uuid}/download`: A short-lived signed URL the browser fetches the archive from directly (https://www.coritan.com/docs/api/reference/client/container-apps/servers-backups/#op-get-api-v1-client-servers-uuid-backups-backup-uuid-download) - `POST /api/v1/client/servers/{uuid}/backups/{backup_uuid}/lock`: Keep a backup, or release it (https://www.coritan.com/docs/api/reference/client/container-apps/servers-backups/#op-post-api-v1-client-servers-uuid-backups-backup-uuid-lock) - `POST /api/v1/client/servers/{uuid}/backups/{backup_uuid}/restore`: Restore from a backup (https://www.coritan.com/docs/api/reference/client/container-apps/servers-backups/#op-post-api-v1-client-servers-uuid-backups-backup-uuid-restore) # Take a server snapshot > Archive a whole server into your account's snapshot storage from its Snapshots tab, and see which snapshots fit it. Source: https://www.coritan.com/docs/managed-containers/snapshots/ In the dashboard: - /dashboard/servers/…/snapshots: https://www.coritan.com/dashboard/servers A snapshot is an archive of a server's files, kept in your account's snapshot storage. It belongs to your account rather than to the server, so it stays after you delete the server and you can restore it onto any of your servers. The server's **Snapshots** tab takes new snapshots and lists the ones you already have. ## Before you begin - Every snapshot uses part of your account's snapshot allowance. The **Snapshot storage** card at the top of the tab shows how much is **Used** and how much is left across every server on the account. [Snapshot storage allowance](/docs/snapshots/storage-allowance/) explains where the allowance comes from. - A snapshot holds the server's files only. The server's [databases](/docs/managed-containers/databases/) are not in it. - You cannot take a snapshot while the server is installing, being moved or suspended. - A server runs one snapshot or restore at a time. - On a server someone shared with you, the owner must have given you the Snapshots permission. ## Take a snapshot 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the server, then the **Snapshots** tab. 2. Select **Take snapshot…**. The dialog measures the server's disk usage and shows it against what is left of your allowance, under **Estimated size against what is left**. 3. Check the **Name**. It starts as `snapshot-` followed by the date and time in UTC, such as `snapshot-2026-09-25-1430`, and can be up to 191 characters. 4. Optional: under **Leave out**, list the files and folders the snapshot should skip, one pattern per line, relative to the server root. For example, `logs/*` skips everything in the `logs` folder. You can list up to 50 patterns. 5. Optional: select **Lock this snapshot**. Nobody can delete a locked snapshot until someone unlocks it, and a schedule never deletes it to make room. The one exception is the cleanup when your account stays over its snapshot storage: it deletes unlocked snapshots first, then locked ones ([Snapshot storage allowance](/docs/snapshots/storage-allowance/)). 6. Select **Take snapshot**. The estimate is the server's current disk usage. We compress the archive, so the finished snapshot is usually smaller. When the estimate is more than what is left, the dialog shows **Not enough snapshot storage** and you cannot take the snapshot. Delete an old snapshot, or select **Order more storage** to open the Snapshot Storage order page, as [Add snapshot storage](/docs/snapshots/add-snapshot-storage/) describes. ## See which snapshots fit this server The list starts on **This server**, which shows the snapshots taken from this server. Select **Whole account** to see every snapshot on your account, with a **Fit** column that says how each one would restore onto this server: **Same software** : The snapshot was taken on this server's software, or on software that runs the same plugins or mods (Paper and Purpur, for example). It restores as it was. **Other loader** : The same game on different server software, such as a Paper world for a Fabric server. The world loads, but its plugins or mods do not. You must allow the difference when you restore it. **Incompatible** : A different game. You cannot restore it onto this server. **Unknown** : We do not know which software the snapshot was taken on. Check that it matches before you restore it. Point at a badge to read the reason. On a server someone shared with you, **Whole account** shows only this server's snapshots, because the rest of the account is not yours to see. **All snapshots** opens the account's [Snapshots page](/docs/snapshots/). ## Read the list Each row shows the snapshot's name, the software it was taken on, its **Status**, its **Size** and when it was **Taken**. Select a column heading to sort by it. - A **Locked** badge beside the name means nobody can delete the snapshot until someone unlocks it. Only the cleanup of an account that stays over its snapshot storage deletes a locked snapshot, after every unlocked one. - A schedule's name under the date means that [schedule](/docs/managed-containers/schedules/) took it. **By the platform** means we took it on our own before removing a server: after a cancellation, for an unpaid invoice, or before cleaning up an inactive free server. - "(probably)" after the software means we recorded the software after the snapshot was taken, from what the server ran later. - A failed snapshot shows its error under its name. - On **Whole account**, a snapshot of another server names that server, with a **Deleted server** badge once the server is gone. The menu at the end of each row holds **Restore onto this server…**, **Download**, **Lock** or **Unlock**, and **Delete snapshot…**. [Restore a snapshot](/docs/snapshots/restore-a-snapshot/) and [Manage snapshots](/docs/snapshots/manage-snapshots/) explain them. ## Result A message confirms that we are taking the snapshot, and it appears in the list straight away. Its **Status** goes from `Pending` to `Creating` while the server archives and uploads its files, then to `Completed`. The tab refreshes on its own while a snapshot is in progress, and the **Size** fills in when it completes. A server that was ordered from a snapshot also shows the state of that restore at the top of the tab: **Waiting to restore your snapshot**, **Restoring your snapshot**, **Restored from a snapshot** or **Could not restore the snapshot**. See [Start a server from a snapshot](/docs/snapshots/new-server-from-a-snapshot/). ## Troubleshooting `This snapshot needs … and your plan includes …` : The snapshot does not fit in what is left of your allowance. Delete an older snapshot or [add snapshot storage](/docs/snapshots/add-snapshot-storage/), then try again. `A snapshot or restore is already in progress on this server; wait for it to finish before you start another snapshot.` : Wait until the snapshot or restore in the list shows `Completed` or `Failed`, then try again. `Cannot start a snapshot while the server is suspended` : A suspended server cannot take snapshots. Try again once the server is unsuspended. The same message names `installing` or `transferring` while the server installs or moves; wait until it finishes. `Could not estimate the size; the snapshot is refused if it does not fit.` : We could not measure the server's disk usage. You can still take the snapshot, and we refuse it if it does not fit. `The daemon never reported this upload finishing. Any partial upload has been released.` : The upload stopped before it finished. We mark a snapshot failed when it has not finished 12 hours after it started. Delete the failed snapshot and take a new one. A failed snapshot does not count against your allowance. `Snapshots are not enabled on this platform yet.` : Snapshots are switched off for now. Try again later, or [contact support](/docs/support/). **Take snapshot** is greyed out : The **Name** is empty, or the estimate does not fit in what is left of your allowance. ## Related - [Restore a snapshot onto a server](/docs/snapshots/restore-a-snapshot/) - [Manage snapshots](/docs/snapshots/manage-snapshots/) - [Snapshot storage allowance](/docs/snapshots/storage-allowance/) - [Snapshots and backups compared](/docs/snapshots/snapshots-and-backups/) ## With the API `GET /api/v1/client/servers/{uuid}/snapshots` lists the snapshots taken from the server, newest first. Add `?scope=account` to list every snapshot on your account instead. Each one then carries a `compatibility` object for this server: `level` is `same`, `same_game`, `incompatible` or `unknown`, and `reason` explains any level but `same`. When the server is shared with you, `scope=account` returns the server's own list. Each snapshot has `uuid`, `name`, `status`, `size_bytes`, `is_locked`, `created_by` (`customer`, `schedule` or `system`), `error`, `region`, `software_label`, `source_uuid`, `source_name`, `source_deleted`, `created_at` and `completed_at`. `status` is `pending` or `creating` while it is taken, `completed` when you can restore or download it, `restoring` or `deleting` while that work runs, or `failed`. Before you take a snapshot, check that it fits: ```bash curl https://api.coritan.com/api/v1/client/servers/$SERVER/snapshots/estimate \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "estimated_bytes": 2147483648, "remaining_bytes": 8589934592, "fits": true, "shortfall_bytes": 0 } ``` Take the snapshot. `name` is required and can be up to 191 characters. `ignored_files` is an optional list of up to 50 patterns, each up to 255 characters and 4,000 characters in all. `is_locked` defaults to `false`. ```bash curl -X POST https://api.coritan.com/api/v1/client/servers/$SERVER/snapshots \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "before-1.21-update", "ignored_files": ["logs/*", "cache/*"], "is_locked": true}' ``` The response is `201` with the new snapshot, its `status` set to `pending`. Poll the list, or `GET /api/v1/client/snapshots/{snapshot_uuid}`, until `status` is `completed`. A snapshot that does not fit answers `402`. Its `detail` holds `message`, `used_bytes`, `allowance_bytes`, `needed_bytes` and `reason` set to `over_allowance`. A second snapshot while one is running, or a server that is installing, moving or suspended, answers `409`. When snapshots are switched off, or no storage is available for the server's region, the API answers `503`. On a server shared with you, listing and the estimate need `snapshot.read`, and taking a snapshot needs `snapshot.create`. The Snapshots permission in the dashboard grants both. ## API - `GET /api/v1/client/servers/{uuid}/snapshots`: List snapshots (https://www.coritan.com/docs/api/reference/client/container-apps/servers-snapshots/#op-get-api-v1-client-servers-uuid-snapshots) - `POST /api/v1/client/servers/{uuid}/snapshots`: Take a snapshot of this server (https://www.coritan.com/docs/api/reference/client/container-apps/servers-snapshots/#op-post-api-v1-client-servers-uuid-snapshots) - `GET /api/v1/client/servers/{uuid}/snapshots/estimate`: How much of the allowance a snapshot of this server would use (https://www.coritan.com/docs/api/reference/client/container-apps/servers-snapshots/#op-get-api-v1-client-servers-uuid-snapshots-estimate) # Schedule server tasks > Run commands, power actions and snapshots on a timetable with schedules, their tasks and their run history. Source: https://www.coritan.com/docs/managed-containers/schedules/ In the dashboard: - /dashboard/servers/…/schedules: https://www.coritan.com/dashboard/servers A schedule runs a list of tasks on a timetable: a console command, a power action or a snapshot. A nightly restart, for example, is a schedule that warns players with a command, waits a minute and then restarts the server. You set schedules up on the server's **Schedules** tab. ## Before you begin - Free servers cannot use schedules. [Free servers](/docs/managed-containers/free-servers/) lists what they leave out. - A server holds up to 25 schedules, and a schedule holds up to 25 tasks. - On a server someone shared with you, the owner must have given you the Schedules permission. To add a task, you also need the permission for what the task does: Command for a console command, Power for a power action, and Snapshots for a snapshot. ## Create a schedule 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the server, then the **Schedules** tab. 2. Select **New schedule…**. 3. Enter a **Name**, such as `Nightly restart`. It can be up to 120 characters. 4. Under **Cron expression**, fill in **Minute**, **Hour**, **Day**, **Month** and **Weekday**. The form starts at `0 4 * * *`, which runs at 04:00 every day. See [Write the timetable](#write-the-timetable). 5. Choose the **Time zone** to read the timetable in. It starts as your browser's time zone. 6. Choose the options: - **Active** runs the schedule on its timetable. Clear it to create the schedule paused. - **Only while the server is online** skips a run when the server is not running. - **Catch up a missed run** runs a missed run once, as soon as possible, when our systems were down at the time it was due. Clear it to skip the missed run and wait for the next one. 7. Select **Create schedule**. The new schedule has no tasks yet, so it does nothing when it runs. Add its tasks next. ## Write the timetable The five fields are standard cron fields, read in the schedule's time zone: | Field | Values | Example | | --- | --- | --- | | **Minute** | `0-59` | `30` runs at half past the hour | | **Hour** | `0-23` | `*/6` runs every six hours | | **Day** | `1-31` | `1` runs on the first of the month | | **Month** | `1-12` | `6-8` runs in June, July and August | | **Weekday** | `0-6`, where `0` is Sunday | `1-5` runs Monday to Friday | Use `*` for any value, a comma for a list (`0,30`), a hyphen for a range (`1-5`) and a slash for a step (`*/15`). Some common timetables: | Timetable | Minute | Hour | Day | Month | Weekday | | --- | --- | --- | --- | --- | --- | | Every day at 04:00 | `0` | `4` | `*` | `*` | `*` | | Every 6 hours | `0` | `*/6` | `*` | `*` | `*` | | Every 15 minutes | `*/15` | `*` | `*` | `*` | `*` | | Mondays at 03:30 | `30` | `3` | `*` | `*` | `1` | Daylight saving follows the time zone. A time that the clocks skip in spring runs once, an hour late, and a time that the clocks repeat in autumn runs once. ## Add tasks 1. Select the schedule's name to open it. It opens on **Tasks**. 2. Select **Add task**. 3. Choose the **Action** and fill in what it needs: - **Run a console command**: the **Command** to send, without a leading `/`, such as `say Restarting in 60 seconds`. It can be up to 1,000 characters. - **Send a power signal**: the **Signal**, which is **Start**, **Stop**, **Restart** or **Kill**. - **Take a snapshot**: nothing else. The snapshot is named `scheduled-` followed by a code for the run and the task. 4. Set the **Delay**: how many seconds to wait after the previous task finishes before this one runs. Leave it at `0` to run at once. A delay can be up to 24 hours (86,400 seconds). 5. Select **Continue if this task fails** if the tasks after it should still run when it fails. Without it, a failed task ends the run. 6. Select **Add task**. Tasks run from the top down. Use **Move up** and **Move down** beside a task to change the order, and **Remove task** to remove it. A console command reaches the server only while it is running, so select **Only while the server is online** for a schedule that starts with a command. A snapshot task uses your account's [snapshot storage](/docs/snapshots/storage-allowance/). When the storage is full, the schedule deletes the oldest unlocked snapshots it took of this server until the new one fits. It never deletes a snapshot that you took by hand, that another schedule took or that we took. If deleting its own snapshots does not make enough room, the task fails. ## Run a schedule now Select **Run now** beside the schedule. It runs once straight away, and the timetable carries on as before. **Run now** is greyed out while the schedule is running. ## Pause, change or delete a schedule - To stop a schedule for a while, open the menu beside **Run now** and select **Pause**. Select **Resume** to start it again. A resumed schedule waits for its next time on the timetable, so resuming a nightly restart does not restart the server there and then. - To change the name, timetable, time zone or options, open the schedule, select **Settings**, then **Edit schedule**. Make your changes and select **Save changes**. - To delete a schedule, open the menu beside **Run now** and select **Delete schedule…**, then **Delete schedule**. Its tasks and run history are deleted with it, and a run in progress stops. ## Check what a schedule did Open the schedule and select **Run history**. It lists the last 20 runs, newest first, with each run's status, when it started and whether the timetable (`Cron`) or someone (`Manual`) started it. Select **Show steps** to see what each task did and any error. | Status | Meaning | | --- | --- | | `Queued` | The run is waiting to start. | | `Running` | The tasks are running. | | `Completed` | Every task ran. | | `Failed` | A task failed and the run stopped. The error shows under the run. | | `Skipped` | The run did not start, for the reason shown under it. | | `Reaped` | The run stopped reporting for an hour, so we ended it. | The schedule's **Settings** view also shows the **Timetable**, the **Next run**, the **Last run** and its **Options**. ## Result A message confirms each change, such as `Schedule created.`, `Task added.` or `Run queued.` The schedule's badge shows **Active**, **Paused** or **Running**, and the next run time appears beside its timetable. ## Troubleshooting `Schedules are not included on the free plan. …` : The server is a free server. To use schedules, [order a paid server](/docs/managed-containers/order-a-server/) and [restore a snapshot](/docs/snapshots/restore-a-snapshot/) of this one onto it. **Last run failed** : A task failed. Open the schedule and select **Run history**, then **Show steps** to read the error. The same error shows under **The last run failed** in **Settings**. `Skipped: server was not running …` : **Only while the server is online** is selected and the server was stopped when the run was due. `Skipped: could not confirm the server was online` means we could not check, so we skipped the run to be safe. `Skipped: server cannot run schedules right now` : The server was installing, being moved or suspended when the run was due, so the run did not start. A run shows `Skipped: server is suspended`, or another status, for the same reason. The schedule runs again at its next time. `Skipped: due time passed while the panel was down` : The run was due while our systems were down, and **Catch up a missed run** is not selected. `A snapshot or restore was already in progress; this run did not take one.` : Another snapshot or restore was running on the server, so the snapshot task was skipped. `This schedule was changed in another tab. Reload and try again.` : Someone saved the schedule after you opened it. Reload the page and make your change again. `This schedule is already running.` : Wait until the current run finishes, then select **Run now** again. `Backups have been replaced by snapshots. …` : The **Take a backup** action can no longer be added. Use **Take a snapshot** instead. Backup tasks added before snapshots replaced backups keep running. `This server already has the maximum of 25 schedules. Delete one before creating another.` : Delete a schedule you no longer need, or add its tasks to another schedule. ## Related - [Take a server snapshot](/docs/managed-containers/snapshots/) - [Use the server console](/docs/managed-containers/console/) - [Share a server with other users](/docs/managed-containers/users/) explains the Schedules permission. ## With the API `GET /api/v1/client/servers/{uuid}/schedules` lists the server's schedules, each with its `tasks`. `POST` to the same path creates one. The body takes `name`, the five cron fields `cron_minute`, `cron_hour`, `cron_day_of_month`, `cron_month` and `cron_day_of_week`, `timezone` (an IANA name, default `UTC`), `is_active` (default `true`), `only_when_online` (default `false`), `catch_up` (default `true`) and an optional `tasks` list, so you can create a schedule and its tasks in one request: ```bash curl -X POST https://api.coritan.com/api/v1/client/servers/$SERVER/schedules \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Nightly restart", "cron_minute": "0", "cron_hour": "4", "cron_day_of_month": "*", "cron_month": "*", "cron_day_of_week": "*", "timezone": "Europe/London", "only_when_online": true, "tasks": [ {"action": "command", "payload": {"command": "say Restarting in 60 seconds"}}, {"action": "power", "payload": {"signal": "restart"}, "time_offset": 60} ] }' ``` Each task has an `action` and a `payload`: | `action` | `payload` | | --- | --- | | `command` | `{"command": "…"}`, up to 1,000 characters | | `power` | `{"signal": "start"}`, `stop`, `restart` or `kill` | | `snapshot` | `{}`, or `{"ignored_files": ["logs/*"]}` to leave files out | `time_offset` is the delay in seconds after the previous task, from `0` to `86400`, and `continue_on_failure` defaults to `false`. The response echoes a power task's signal as `{"action": "restart"}`. | Request | What it does | | --- | --- | | `GET /schedules/{schedule_uuid}` | Returns one schedule. | | `PUT /schedules/{schedule_uuid}` | Changes any schedule field. Send the `revision` you read to be told with a `409` when someone else saved first. | | `DELETE /schedules/{schedule_uuid}` | Deletes the schedule, its tasks and its history. | | `POST /schedules/{schedule_uuid}/tasks` | Adds a task at the end, or at `sequence_id`. | | `PUT /schedules/{schedule_uuid}/tasks/{task_id}` | Changes a task. Changing `action` needs a new `payload`. | | `DELETE /schedules/{schedule_uuid}/tasks/{task_id}` | Removes a task. | | `POST /schedules/{schedule_uuid}/tasks/reorder` | Sets the order. `task_ids` must list every task once. | | `POST /schedules/{schedule_uuid}/execute` | Queues a run now and returns its `run_id`. | | `GET /schedules/{schedule_uuid}/runs` | Lists recent runs with their `steps`. `limit` is 1 to 100, default 20. | The paths are relative to `/api/v1/client/servers/{uuid}`. A schedule's response also carries `next_run_at`, `last_run_at`, `last_run_status`, `last_failure_message`, `is_processing` and `revision`. `GET /api/v1/client/timezones` lists the time zones a schedule accepts, as `zones` with each zone's `name` and current `offset`, and `aliases` that map old zone names to current ones. An unknown zone answers `400` with `Unknown timezone`, and a cron that is not five valid fields answers `400` too. On a server shared with you, listing needs `schedule.read`, creating needs `schedule.create`, deleting needs `schedule.delete`, and every other change needs `schedule.update`. A task also needs the permission its action uses: `control.console` for a command, `control.start`, `control.stop`, `control.restart` or `control.kill` for a power signal, and `snapshot.create` for a snapshot. ## API - `GET /api/v1/client/servers/{uuid}/schedules`: List schedules for a server (https://www.coritan.com/docs/api/reference/client/container-apps/servers-schedules/#op-get-api-v1-client-servers-uuid-schedules) - `POST /api/v1/client/servers/{uuid}/schedules`: Create a new schedule, optionally with its whole task pipeline (https://www.coritan.com/docs/api/reference/client/container-apps/servers-schedules/#op-post-api-v1-client-servers-uuid-schedules) - `GET /api/v1/client/servers/{uuid}/schedules/{schedule_uuid}`: Get a schedule (https://www.coritan.com/docs/api/reference/client/container-apps/servers-schedules/#op-get-api-v1-client-servers-uuid-schedules-schedule-uuid) - `PUT /api/v1/client/servers/{uuid}/schedules/{schedule_uuid}`: Update a schedule (https://www.coritan.com/docs/api/reference/client/container-apps/servers-schedules/#op-put-api-v1-client-servers-uuid-schedules-schedule-uuid) - `DELETE /api/v1/client/servers/{uuid}/schedules/{schedule_uuid}`: Delete a schedule (https://www.coritan.com/docs/api/reference/client/container-apps/servers-schedules/#op-delete-api-v1-client-servers-uuid-schedules-schedule-uuid) - `POST /api/v1/client/servers/{uuid}/schedules/{schedule_uuid}/execute`: Queue a schedule to run now, without changing its next scheduled run (https://www.coritan.com/docs/api/reference/client/container-apps/servers-schedules/#op-post-api-v1-client-servers-uuid-schedules-schedule-uuid-execute) - `GET /api/v1/client/servers/{uuid}/schedules/{schedule_uuid}/runs`: Recent runs of a schedule, newest first, with the outcome of every step (https://www.coritan.com/docs/api/reference/client/container-apps/servers-schedules/#op-get-api-v1-client-servers-uuid-schedules-schedule-uuid-runs) - `POST /api/v1/client/servers/{uuid}/schedules/{schedule_uuid}/tasks`: Append a task to a schedule's pipeline (https://www.coritan.com/docs/api/reference/client/container-apps/servers-schedules/#op-post-api-v1-client-servers-uuid-schedules-schedule-uuid-tasks) - `POST /api/v1/client/servers/{uuid}/schedules/{schedule_uuid}/tasks/reorder`: Set the order tasks run in (https://www.coritan.com/docs/api/reference/client/container-apps/servers-schedules/#op-post-api-v1-client-servers-uuid-schedules-schedule-uuid-tasks-reorder) - `PUT /api/v1/client/servers/{uuid}/schedules/{schedule_uuid}/tasks/{task_id}`: Edit a task in place (https://www.coritan.com/docs/api/reference/client/container-apps/servers-schedules/#op-put-api-v1-client-servers-uuid-schedules-schedule-uuid-tasks-task-id) - `DELETE /api/v1/client/servers/{uuid}/schedules/{schedule_uuid}/tasks/{task_id}`: Remove a task from a schedule (https://www.coritan.com/docs/api/reference/client/container-apps/servers-schedules/#op-delete-api-v1-client-servers-uuid-schedules-schedule-uuid-tasks-task-id) - `GET /api/v1/client/timezones`: Every zone this platform can resolve, west to east, with its old names (https://www.coritan.com/docs/api/reference/client/container-apps/timezones/#op-get-api-v1-client-timezones) # Manage server ports > Add, remove and label your server's ports, choose its primary address and publish a port on a floating IP. Source: https://www.coritan.com/docs/managed-containers/ports/ In the dashboard: - /dashboard/servers/…/ports: https://www.coritan.com/dashboard/servers Every server has a *primary port*: the address players join. You can add more ports for a voice plugin, a map viewer, or a query or RCON listener. The server's **Ports** tab lists them, adds and releases them, and chooses which one is primary. With a [floating IP](/docs/floating-ips/) attached, you can also open ports on that address and publish a port on the game's default port, so players join with the address alone. ## Before you begin - The plan sets how many ports the server can hold, including the primary one. Most plans allow 5. The **Ports in use** figure on the tab shows how many you have used, such as `2 of 5`, and **Port slots** on the [Settings tab](/docs/managed-containers/settings/) shows the limit. - Extra ports on the machine's shared address come from the range 10000–40000. A plugin that should use one must be set to listen on that port number. - On a server someone shared with you, you need the `allocation` permissions. The dashboard's permission list has no option for them, so the owner can grant them only [through the API](/docs/managed-containers/users/#with-the-api). ## Read the Ports tab The three figures at the top show the **Ports in use** against the plan's limit, the **Managed ports** that a feature added, and the **Primary address** with a button to copy it. The table lists every port: **Address** : The address players or apps connect to, with a button to copy it. When a floating IP changes how the port is reached, the address on the machine's own IP shows under it as `direct`. **Purpose** : A **Primary** badge marks the primary port, **Managed** marks a port a feature added (such as [Bedrock crossplay](/docs/managed-containers/software/bedrock-crossplay/)), and **Floating IP** marks a port on the server's floating IP. Other ports show what they are for, such as `Extra`, `Query` or `RCON`. **Published on** : The game's default port this port answers on, if you published it. **Notes** : The note you gave the port. **Added** : When the port was added. Managed ports do not count against the plan's limit. You cannot release them; turning the feature off releases them. ## Add a port 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the server, then the **Ports** tab. 2. Select **Add port…**. 3. Choose where the port comes from: - **Lowest free port** takes the next free port in the machine's range. - **Choose a port** lets you pick one from the **Free port** list. - **On the floating IP** opens a port on the server's floating IP. Enter a **Port** from 1024 to 65535, or leave it empty for the first free one. The field's hint lists the ports the host itself uses, which you cannot open. 4. Optional: enter a **Note**, such as `voice` or `dynmap`, so you remember what the port is for. It can be up to 64 characters. 5. Select **Add port**. The server listens on the new port the next time it starts, so restart it once the plugin is set up. Ports on the floating IP do not count against the plan's limit, and a server can hold up to 16 of them. ## Make another port primary 1. Open the menu at the end of the port's row and select **Make primary…**. 2. Select **Make primary**. The server's main listener moves to that port, and it becomes the address the dashboard shows. If the server is running, we restart it so it listens on the new port. The menu does not offer this for managed ports. ## Publish a port on the default game port With a floating IP attached, a port can answer on the game's default port, so players type the floating IP without a port number. 1. Open the menu at the end of the port's row and select **Publish on the default port…**. 2. Under **Publish on**, choose **Java (25565)** or **Bedrock (19132)**. 3. Select **Publish port**. We move the port onto that number and restart the server if it is running. Only one port can hold each default port at a time. To undo it, open the menu, select **Change the published port…**, then **Stop publishing**. A managed port, or a port that is already on the floating IP, cannot be published. ## Release a port 1. Open the menu at the end of the port's row and select **Release port…**. 2. Select **Release port**. Anything listening on the port stops being reachable. A port on the shared address goes back into the pool, and we may give it to another server. You cannot release the primary port or a managed port. While a floating IP is attached, the server keeps at least one port on the shared address, because it falls back to that port if the floating IP is detached. ## Give the server a web address The **Web address** card at the bottom of the tab puts a hostname in front of the primary port, with an HTTPS certificate and our edge's filtering. Use it for a web map, a panel or an API. 1. Enter a **Hostname**, such as `map.example.com`. The field suggests a name when your account holds a Coritan domain. 2. Leave **Request an HTTPS certificate** selected unless you do not want one. 3. Select the button under the form to give the server a web address. When we host the domain's DNS, we write the record for you. Otherwise, point the name at our edge yourself. The card then lists the address, and **Manage route** opens it in [Edge Proxy](/docs/proxies/). ## Result A message confirms each change, such as `Port 25567 added.`, `Port 25567 released.` or `25567 is now the primary port.` The table updates straight away. ## Troubleshooting **Add port…** is greyed out : Every port the plan allows is in use, and the server has no floating IP with room. Release a port, or move to a plan with more ports. `Allocation limit reached (5). Cannot add more ports to this server.` : The server holds every port its plan allows. Release a port first. The number in the message is your plan's limit. `Port must be between 10000 and 40000` : Ports on the shared address come from that range. Use **On the floating IP** for any other port. `Port 25565 is reserved on the node main IP (gameproxy / platform services)` : Our own services use that port on the shared address. Choose another port. `Port 25567 is already in use on this node` : Another server has that port. Choose another one from the **Free port** list. `Cannot release the primary allocation` : Make another port primary first, then release this one. `This port is managed by the platform (…). Turn the feature that uses it off to release it.` : A feature such as Bedrock crossplay uses the port. Turn the feature off to release it. `Keep at least one shared node port: it is what this server falls back to if the floating IP is detached.` : Keep this port while the floating IP is attached. `Attach a floating IP first; …` : Opening a port on a floating IP, or publishing on a default port, needs a floating IP attached to the server. See [Floating IPs](/docs/floating-ips/). `Another port on this server already publishes on 25565` : Stop publishing the other port first. ## Related - [Server settings and resources](/docs/managed-containers/settings/) - [Floating IPs](/docs/floating-ips/) - [Let Bedrock players join a Java server](/docs/managed-containers/software/bedrock-crossplay/) ## With the API `GET /api/v1/client/servers/{uuid}/allocations` lists the server's ports. Each has `id`, `ip`, `port`, `is_primary`, `purpose`, `label`, `managed`, `dedicated` (on the floating IP), `notes`, `publish_port` and `created_at`, and `endpoint`, the `host:port` players use. `GET /api/v1/client/servers/{uuid}/allocations/rules` returns the limits: `allocation_limit` and `allocation_used` for the shared range, `publish_ip` (the floating IP, or `null`), and `dedicated_ip` with the floating IP's `address`, `port_min`, `port_max`, `reserved_ports`, `limit` and `used`, or `null` without one. `game` names the server's game and its `default_port`. `GET /api/v1/client/servers/{uuid}/allocations/available-ports` returns up to `limit` free ports in the shared range (1 to 200, default 50) as `ports`. Add a port. Leave out `port` for the lowest free one, and set `on_dedicated_ip` to `true` to open it on the floating IP: ```bash curl -X POST https://api.coritan.com/api/v1/client/servers/$SERVER/allocations \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"port": 25567, "notes": "voice"}' ``` The response is `201` with the new port. | Request | What it does | | --- | --- | | `DELETE /allocations/{allocation_id}` | Releases the port. | | `POST /allocations/{allocation_id}/primary` | Makes the port primary. The response adds a `publish` object. | | `POST /allocations/{allocation_id}/publish-port` | Publishes the port on `{"port": 25565}` or `{"port": 19132}`, or stops with `{"port": null}`. Returns `allocation` and `publish`. | The paths are relative to `/api/v1/client/servers/{uuid}`. In `publish`, `restart` is `scheduled` when we restart the server to rebind it. Every refusal answers `400` with the reason in `detail`. On a server shared with you, listing needs `allocation.read`, adding needs `allocation.create`, releasing needs `allocation.delete`, and making a port primary or publishing it needs `allocation.update`. ## API - `GET /api/v1/client/servers/{uuid}/allocations`: List primary + extra ports for this server (https://www.coritan.com/docs/api/reference/client/container-apps/servers-allocations/#op-get-api-v1-client-servers-uuid-allocations) - `POST /api/v1/client/servers/{uuid}/allocations`: Create server allocation (https://www.coritan.com/docs/api/reference/client/container-apps/servers-allocations/#op-post-api-v1-client-servers-uuid-allocations) - `GET /api/v1/client/servers/{uuid}/allocations/available-ports`: Free ports on this server's node (node-wide, not shared across servers) (https://www.coritan.com/docs/api/reference/client/container-apps/servers-allocations/#op-get-api-v1-client-servers-uuid-allocations-available-ports) - `GET /api/v1/client/servers/{uuid}/allocations/rules`: Allocation rules (https://www.coritan.com/docs/api/reference/client/container-apps/servers-allocations/#op-get-api-v1-client-servers-uuid-allocations-rules) - `DELETE /api/v1/client/servers/{uuid}/allocations/{allocation_id}`: Release an extra port (https://www.coritan.com/docs/api/reference/client/container-apps/servers-allocations/#op-delete-api-v1-client-servers-uuid-allocations-allocation-id) - `POST /api/v1/client/servers/{uuid}/allocations/{allocation_id}/primary`: Mark an existing port as the primary allocation (https://www.coritan.com/docs/api/reference/client/container-apps/servers-allocations/#op-post-api-v1-client-servers-uuid-allocations-allocation-id-primary) - `POST /api/v1/client/servers/{uuid}/allocations/{allocation_id}/publish-port`: Set allocation publish port (https://www.coritan.com/docs/api/reference/client/container-apps/servers-allocations/#op-post-api-v1-client-servers-uuid-allocations-allocation-id-publish-port) # Share a server with other users > Give another Coritan account access to your server and choose exactly what it can do, permission by permission. Source: https://www.coritan.com/docs/managed-containers/users/ In the dashboard: - /dashboard/servers/…/users: https://www.coritan.com/dashboard/servers The **Users** tab gives another Coritan account access to your server. The other person signs in to their own account, finds the server on their **Container Apps** page and can do only what you allow. You never share your password, and billing stays with you: other users cannot see your invoices, change the plan or cancel the server. ## Before you begin - The other person needs a Coritan account. You add them by its user ID, a number. No dashboard page shows it yet, so ask them to read `id` in the answer to `GET /api/v1/auth/me`, as [Profile](/docs/account/profile/#with-the-api) shows. - You must own the server, or hold the Manage users permission on it. - We do not email the other person or ask them to accept. Tell them yourself once you have added them. ## Add a user 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the server, then the **Users** tab. 2. Select **Add user…**. 3. In **User ID**, enter the other account's user ID, for example `10482`. 4. Under **Permissions**, tick what they may do. [Choose permissions](#choose-permissions) explains each one. 5. Select **Add user**. The user joins the list, and the server appears on their **Container Apps** page. ## Choose permissions Each permission in the list covers one area of the server: | Permission | What it allows | |---|---| | Power | Start, stop, restart and kill the server, and wake a sleeping free server. | | Command | Run commands in the console. | | Console | Open the console. It grants the same as Command, so once you save either one, the list shows both. | | Files | Browse, edit, upload, download and delete files, compress and extract archives, connect over [SFTP](/docs/managed-containers/sftp/) and [import files from another host](/docs/managed-containers/import/). | | Snapshots | Take snapshots of this server, list them and restore a snapshot onto this server. | | Backups | List, restore, download, lock and delete the server's [backups](/docs/managed-containers/backups/). | | Databases | Create databases, see their passwords, set new passwords and delete databases. | | Schedules | Create, change, run and delete [schedules](/docs/managed-containers/schedules/). Each task also needs the permission for its action. | | Manage users | Add, change and remove other users on this server. | | Manage rules | Create, change and delete [rules](/docs/managed-containers/rules/). | | Install software | Search the catalogue, install and update plugins, mods and packs, turn them on and off, and set the resource pack. | > [!WARNING] > A user with Manage users can give anyone any permission, including permissions they do not hold themselves. Give it only to someone you would trust with the whole server. Some things need a permission that the list does not offer, and you can grant these only [through the API](#with-the-api): - The **Ports** tab needs the `allocation` permissions. - The Java settings need `startup.read` and `startup.update`. - Removing a plugin, mod, pack or the resource pack needs `software.delete`. - Changing the server's software needs `settings.reinstall` as well as Install software. - Starting in safe mode needs Power as well as Install software. Downloading, locking and deleting a snapshot work only from the account that holds it, so another user cannot do them even with the Snapshots permission. ## Change what a user can do 1. On the **Users** tab, open the menu at the end of the user's row and select **Edit permissions…**. 2. Tick or clear permissions. 3. Select **Save permissions**. The change applies straight away. If the user has the console open, it closes, and the new permissions apply when they open it again. Saving keeps any permission you granted through the API that the list does not show, such as `allocation.read`. A wildcard is the exception: saving replaces `*`, or a whole-area grant such as `file.*`, with the permissions of the boxes that are ticked. ## Remove a user 1. On the **Users** tab, open the menu at the end of the user's row and select **Remove user…**. 2. Select **Remove user** to confirm. They lose access to the server at once, and any console they have open closes. Their account and their own servers are not affected. ## What the other user sees - The server is on their **Container Apps** page, with every tab. An action they have no permission for fails with `Insufficient permissions`. - On the **Users** tab they see the other users by name, without email addresses. They see their own email address, but not yours. - They connect over SFTP with their own email address and password. ## Result The dashboard confirms each change with `User added.`, `Permissions updated.` or `User removed.`. The **Access** column lists each user's permissions, and the line under their name shows how many permission keys they hold and when you added them. ## Troubleshooting `That user already owns this server` : You entered your own user ID. Enter the other person's. `User 10482 is already a subuser on this server` : That account already has access. Use **Edit permissions…** on its row instead. The same message appears when no account has that user ID, so check the number with the other person. `Insufficient permissions` : The user tried something their permissions do not cover. The owner adds the permission with **Edit permissions…**. **No permissions this panel knows of** : The user holds only permissions that the list does not show, granted through the API. They keep them when you save. The server is missing from the other person's **Container Apps** page : Check that they signed in to the account whose user ID you entered, then ask them to reload the page. ## Related - [Use the console and power controls](/docs/managed-containers/console/) - [Connect to a server with SFTP](/docs/managed-containers/sftp/) - [Schedule server tasks](/docs/managed-containers/schedules/) - [Manage server ports](/docs/managed-containers/ports/) ## With the API `GET /api/v1/client/servers/{uuid}/users` lists the users on a server. Each entry has `id` (the entry's own number), `user_id` (the account's user ID), `permissions`, `created_at`, `email` and `name`. When a user who is not the owner asks, `email` is `null` on every entry but their own. Add a user with `POST /api/v1/client/servers/{uuid}/users`. The body takes `user_id` and `permissions`, a list of permission keys: ```bash curl -X POST https://api.coritan.com/api/v1/client/servers/$SERVER/users \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"user_id": 10482, "permissions": ["control.console", "control.start", "control.stop", "control.restart", "file.read", "file.read-content"]}' ``` The response (`201`) is the new entry: ```json { "id": 318, "server_id": 2207, "user_id": 10482, "permissions": ["control.console", "control.start", "control.stop", "control.restart", "file.read", "file.read-content"], "created_at": "2026-09-25T14:30:00", "email": "sam@example.com", "name": "Sam Taylor" } ``` The other routes take the entry's `id`, not the user ID: | Route | What it does | |---|---| | `PUT /api/v1/client/servers/{uuid}/users/{subuser_id}` | Replaces the user's permissions with the `permissions` list in the body. | | `DELETE /api/v1/client/servers/{uuid}/users/{subuser_id}` | Removes the user and answers `{"message": "Subuser removed"}`. | These are the permission keys, by area. `*` grants every key, and an area followed by `.*`, such as `file.*`, grants every key in that area. The list must hold at least one key. | Area | Keys | What the dashboard's list grants | |---|---|---| | `control` | `console`, `start`, `stop`, `restart`, `kill` | Command and Console grant `console`; Power grants the other four. | | `file` | `read`, `read-content`, `create`, `update`, `update-content`, `delete`, `archive`, `sftp` | Files grants all eight. | | `snapshot` | `create`, `read`, `delete`, `restore`, `download` | Snapshots grants all five. | | `backup` | `create`, `read`, `delete`, `restore`, `download` | Backups grants all five. | | `database` | `create`, `read`, `update`, `delete`, `view_password` | Databases grants all five. | | `schedule` | `create`, `read`, `update`, `delete` | Schedules grants all four. | | `user` | `create`, `read`, `update`, `delete` | Manage users grants all four. | | `settings` | `automation`, `reinstall`, `rename` | Manage rules grants `automation`. | | `software` | `search`, `read`, `install`, `update`, `delete` | Install software grants `search`, `read` and `install`. | | `allocation` | `read`, `create`, `update`, `delete` | None. | | `startup` | `read`, `update`, `docker-image` | None. | A key is the area and the name joined by a full stop, for example `file.read-content`. We accept `settings.rename`, `software.update` and `startup.docker-image`, but nothing checks them yet. Each guide in this section names the keys its routes need. Adding a user needs `user.create`, changing one needs `user.update` and removing one needs `user.delete`. Any user on the server can list the users. Errors answer `400` with the reasons in [Troubleshooting](#troubleshooting), `Unknown permission: file.rename` for a key that does not exist and `Select at least one permission` for an empty list. A `subuser_id` that is not on the server answers `404` with `Subuser not found`. ## API - `GET /api/v1/client/servers/{uuid}/users`: List subusers for a server (https://www.coritan.com/docs/api/reference/client/container-apps/servers-users/#op-get-api-v1-client-servers-uuid-users) - `POST /api/v1/client/servers/{uuid}/users`: Add a subuser to a server (https://www.coritan.com/docs/api/reference/client/container-apps/servers-users/#op-post-api-v1-client-servers-uuid-users) - `PUT /api/v1/client/servers/{uuid}/users/{subuser_id}`: Update subuser permissions (https://www.coritan.com/docs/api/reference/client/container-apps/servers-users/#op-put-api-v1-client-servers-uuid-users-subuser-id) - `DELETE /api/v1/client/servers/{uuid}/users/{subuser_id}`: Remove a subuser from a server (https://www.coritan.com/docs/api/reference/client/container-apps/servers-users/#op-delete-api-v1-client-servers-uuid-users-subuser-id) # Change the server software > Switch your server to other software, or to another version of the same software, and follow the install job. Source: https://www.coritan.com/docs/managed-containers/software/ In the dashboard: - /dashboard/servers/…/software: https://www.coritan.com/dashboard/servers The **Software** tab shows what your server runs and switches it to other software, or to another version of the same software. A change is a reinstall that runs as a job: you choose whether to keep the server's files or start clean. The tab's other panels add and manage what runs on top of the software: - **Marketplace** installs plugins, mods, datapacks and modpacks: see [Install plugins, mods and modpacks](/docs/managed-containers/software/plugins-and-mods/). - **Installed addons** turns them on or off, updates and removes them: see [Manage installed addons](/docs/managed-containers/software/installed-addons/). - **Java** sets the memory, startup flags and Java version: see [Change Java settings](/docs/managed-containers/software/java-settings/). - **Resource pack** and **Safe mode** are covered in [Set a server resource pack](/docs/managed-containers/software/resource-pack/) and [Start a server in safe mode](/docs/managed-containers/software/safe-mode/). - **Packs** replaces the last four on a Bedrock server: see [Manage Bedrock packs](/docs/managed-containers/software/bedrock-packs/). The panels a server shows depend on its software. **Java** appears only on software that runs on Java, for example. ## Before you begin - Take a [snapshot](/docs/managed-containers/snapshots/) first. **Start clean** deletes every file on the server, and moving a world to a newer game version can change it for good, even with **Keep my files**. - The change stops the server, so tell your players. - On a server someone shared with you, a change needs `settings.reinstall` as well as the Install software permission. The dashboard's permission list has no option for `settings.reinstall`, so the owner can grant it only [through the API](/docs/managed-containers/users/#with-the-api). ## Read the current software The **Current software** card at the top of the tab shows what the server runs now: **Software** : The software's name, such as Paper or Fabric. **Version** : The game version, such as `1.21.4`. **Edition** : `Bedrock`, shown only for a Bedrock server. **Loader** : The loader, with its build when you chose one. **Java** : The Java version the server runs on. **Jar** : The file the server starts, such as `server.jar`. **Modpack** : The modpack you installed from the Marketplace, if any. On a Java server that can take Bedrock players, the card also has the **Bedrock crossplay** switch. [Let Bedrock players join a Java server](/docs/managed-containers/software/bedrock-crossplay/) explains it. ## Switch software or change the version 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the server, then the **Software** tab. The **Server software** panel opens first. 2. Under **Choose the software to run**, select the software. To move to another version of what you run now, select the entry marked **Current**. **Search software** filters the list. Entries marked **Experimental** or **Deprecated** install like the others, but their publishers do not recommend them for a main server. 3. Choose a version. The newest 12 show first: select the button under them for the rest, or type in **Search versions**. Select **Show snapshots and unsupported builds** to list snapshots and versions we do not offer by default. 4. For Fabric, Quilt, Forge and NeoForge, choose a **Loader build**, or leave it on **Latest build**. A modpack names the loader version it needs. 5. Choose the **Java runtime**. The field's hint names the runtime we recommend for that version. You cannot choose a Java version older than the one the game version needs. 6. Choose what happens to the files: - **Keep my files** swaps the server jar, the startup command and the Java runtime. Worlds, plugins and configs stay. - **Start clean** deletes every file on the server first, then installs the software fresh. 7. Select **Switch software**, or **Change version** when you stay on the same software. With **Start clean**, the button is **Wipe and install…**: type the server's name to confirm, then select **Wipe and install**. When you switch to software with a different loader and keep your files, plugins and mods written for the old loader do not load on the new one. They stay on the server, so you can remove them or find new versions afterwards. ## What the change does The job runs in this order: 1. It waits for any other software job on the server to finish first. 2. It stops the server. With **Keep my files**, we ask the server to stop and force it after a minute. With **Start clean**, we force it straight away. 3. With **Start clean**, it deletes the server's files and our records of what the Marketplace installed. 4. It applies the new software's startup command, Java runtime and ports. Software that cannot run [Bedrock crossplay](/docs/managed-containers/software/bedrock-crossplay/) has it turned off. 5. It downloads and installs the software, then checks that the file the server starts is on disk. While the new software installs, the server shows `Installing`. The job does not start the server for you: start it from the [console](/docs/managed-containers/console/) when the job shows `Succeeded`. ## Follow and cancel jobs Every software job on the server appears in a card above the panels: changes, installs and updates. While a job runs, the card counts the running jobs, such as `1 job running`, and updates every few seconds. When nothing runs, the card is **Recent jobs** and shows the last three. Each row shows the job, the version it asked for, its status and its current step, such as `Stopping` or `Installing`, with a progress bar. A row marked **Stalled** has not reported progress for a while; we pick it up again automatically. - To stop a job that is queued or running, select **Cancel job**. A queued job never starts. A running job stops at its next step. - To run a failed install again, select **Try again**. If a change fails or is cancelled after the old software was removed, we check the server's files. When the new software is not there, we retry the install ourselves. ## Result A message confirms the change was queued, and the job appears in the jobs card. Its status moves from `Queued` to `Running`, then to `Succeeded`, `Succeeded with warnings`, `Failed` or `Cancelled`. After a change, the **Current software** card shows the new software and version. ## Troubleshooting **Try again** fails on a software change : It fails with `mode must be one of: add, replace, update`, because **Try again** handles only installs at the moment. Choose the software and version again and start a new change. `Another software job is running. This one queues behind it.` : The form shows this while another job runs. Your change waits for it and then runs alone. **The installed software does not match the plan** : The software on disk uses a different loader from the software the server was set up with, for example after you installed a Fabric modpack on a Paper server. Choose the software you want under **Server software** so the startup command, the Java runtime and the addon folders match. `The startup command points at a different jar than the one installed. Switching version under Server software rewrites it.` : Change to the version you want, even the one you run now with another loader build, to fix the startup command. `Type the server name to confirm a wipe and reinstall` : The name you typed did not match. Type the server's name exactly as the dashboard shows it. `Minecraft Paper has no installable versions right now.` : The software's publisher did not answer with any versions. Try again later, or choose a specific version. `Wings reported the install script failed, so the new software is not on disk.` : The install failed on the server. The message ends with the last lines of the install's output. We retry the install automatically. `Too many requests for this action. Please wait and try again.` : A server accepts 8 software actions from one account in 10 minutes. Changes, installs and updates all count. The response says how many seconds to wait. The change answers `423` : We have locked the server for review. The message tells you to reply to the support ticket we opened. ## Related - [Install plugins, mods and modpacks](/docs/managed-containers/software/plugins-and-mods/) - [Change Java settings](/docs/managed-containers/software/java-settings/) - [Take a server snapshot](/docs/managed-containers/snapshots/) - [Server limits and rate limits](/docs/managed-containers/limits/) ## With the API `GET /api/v1/client/servers/{uuid}/software/context` returns what the server runs: `specialization_slug` and `specialization_name`, `mcjars_type`, `loader`, `loader_family`, `loader_build`, `game_version`, `edition`, `runtime_template_slug`, `runtime_java`, `jar_filename`, `supported_kinds` and `install_dirs`. It also carries `needs_software_change`, `startup_mismatch`, `bedrock_bridge`, `safe_mode`, `active_installs` and `install_busy`, which is `true` while a job that must run alone is queued or running. `GET /api/v1/client/servers/{uuid}/software/catalog` lists the software the server can switch to. `groups` holds each heading's `key`, `label` and `items`. An item's `key`, such as `minecraft-paper`, is what a change sends. It also has `name`, `description`, `loader`, `experimental`, `deprecated`, `is_current` and `required_variables`. `GET /software/catalog/{key}/versions` lists one entry's versions, newest first. Each has `version_id`, `name`, `required_java`, `supported` and `recommended_runtime_slug`. `runtimes` lists the Java runtimes by `slug` and `name`, and `current_version` is the version the server runs now. Add `include_unsupported=true` for snapshots and unsupported versions, and `limit` from 1 to 500 (default 200). `GET /software/catalog/{key}/versions/{version}/builds` lists the loader builds of one version for Fabric, Quilt, Forge and NeoForge, each with a `build_number`. Queue a change: ```bash curl -X POST https://api.coritan.com/api/v1/client/servers/$SERVER/software/change \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"target_key": "minecraft-paper", "version": "1.21.4", "runtime_template_slug": "java-21", "mode": "keep"}' ``` | Field | What it holds | | --- | --- | | `target_key` | The catalogue entry's `key`. Required. | | `version` | A `version_id`, or `latest` (the default). | | `runtime_template_slug` | The Java runtime. Leave it out for the recommended one. | | `build` | A loader build number. Leave it out for the newest build. | | `mode` | `keep` (the default) or `wipe`. | | `confirm_server_name` | The server's name. Required with `wipe`. | | `variables` | Values the software needs, when `required_variables` names any. | The response is the job, with `kind` set to `software_change`. Sending a change while the same change is queued or running returns that job. A refusal answers `400` with the reason in `detail`, and an unknown key answers `Specialization minecraft-papr not found`. | Request | What it does | | --- | --- | | `GET /software/installs` | Lists the server's software jobs, newest first, up to 50. Add `status=active` for the queued and running ones, or any single status. | | `GET /software/installs/{install_uuid}` | Returns one job. | | `POST /software/installs/{install_uuid}/cancel` | Cancels a queued or running job and returns it. A finished job answers `409`, such as `Install is already succeeded`. | | `POST /software/installs/{install_uuid}/retry` | Queues a finished install again with the same settings and returns the new job. A job that is still running answers `409` with `That install is still in progress`. | The paths are relative to `/api/v1/client/servers/{uuid}`. A job has `uuid`, `kind`, `identifier`, `mode`, `requested_version` and `resolved_version`, `status`, `stage`, `progress` from 0 to 100, `error` and `error_code` when it fails, `stalled`, `cancel_requested`, and `created_at`, `started_at` and `finished_at`. `detail` holds what the job did, including `warnings` when it finished with some. On a server shared with you, reading needs `software.read`, a change needs `settings.reinstall` and `software.install`, and cancelling or retrying needs `software.install`. ## API - `GET /api/v1/client/servers/{uuid}/software/context`: Return loader/game-version context for marketplace tab gating (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-get-api-v1-client-servers-uuid-software-context) - `GET /api/v1/client/servers/{uuid}/software/catalog`: Server software the machine can be switched to, grouped for display (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-get-api-v1-client-servers-uuid-software-catalog) - `GET /api/v1/client/servers/{uuid}/software/catalog/{key}/versions`: Installable versions for one catalog entry, with runtime recommendations (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-get-api-v1-client-servers-uuid-software-catalog-key-versions) - `GET /api/v1/client/servers/{uuid}/software/catalog/{key}/versions/{version}/builds`: Publisher builds of one game version: the loader versions for Fabric/Forge (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-get-api-v1-client-servers-uuid-software-catalog-key-versions-version-builds) - `POST /api/v1/client/servers/{uuid}/software/change`: Switch the server to different software, or a different version of it (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-post-api-v1-client-servers-uuid-software-change) - `GET /api/v1/client/servers/{uuid}/software/installs`: List recent software install jobs for a server (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-get-api-v1-client-servers-uuid-software-installs) - `GET /api/v1/client/servers/{uuid}/software/installs/{install_uuid}`: Get a single software install job (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-get-api-v1-client-servers-uuid-software-installs-install-uuid) - `POST /api/v1/client/servers/{uuid}/software/installs/{install_uuid}/cancel`: Cancel a queued software install (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-post-api-v1-client-servers-uuid-software-installs-install-uuid-cancel) - `POST /api/v1/client/servers/{uuid}/software/installs/{install_uuid}/retry`: Queue a fresh install job with the same parameters as a failed one (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-post-api-v1-client-servers-uuid-software-installs-install-uuid-retry) # Install plugins, mods and modpacks > Search the marketplace for plugins, mods, datapacks and modpacks, check their dependencies and install them. Source: https://www.coritan.com/docs/managed-containers/software/plugins-and-mods/ In the dashboard: - /dashboard/servers/…/software: https://www.coritan.com/dashboard/servers The **Marketplace** panel on a server's **Software** tab searches the addon sources we connect to, such as Modrinth, and installs what you choose onto the server. Each install runs as a job that you can follow, cancel and retry. We keep a record of everything the Marketplace installs, so you can update or remove it later on the [Installed addons](/docs/managed-containers/software/installed-addons/) panel. ## Before you begin - The server must be installed. - Take a [snapshot](/docs/managed-containers/snapshots/) before you install a modpack. A modpack stops the server, adds its files on top of yours and can switch the server to other software. - On a server someone shared with you, the Install software permission lets you search and install. Removing an install needs `software.delete`, which the dashboard's permission list does not offer, so the owner can grant it only [through the API](/docs/managed-containers/users/#with-the-api). What you can search for depends on the server's software: | Software | What the Marketplace offers | | --- | --- | | Plugin servers, such as Paper, Purpur and Spigot | Plugins, modpacks, datapacks and resource packs | | Mod loaders: Fabric, Quilt, Forge and NeoForge | Mods, modpacks, datapacks and resource packs | | Arclight, Mohist and Magma, which load both | Plugins, mods, modpacks, datapacks and resource packs | | Vanilla | Modpacks, datapacks and resource packs | | Velocity and BungeeCord | Plugins. NanoLimbo loads no addons. | | Bedrock Dedicated Server | Bedrock add-ons and resource packs, which [Manage Bedrock packs](/docs/managed-containers/software/bedrock-packs/) covers | ## Find an addon 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the server, then the **Software** tab. 2. Select **Marketplace**. 3. In the list before the search field, choose what to look for: **Plugins**, **Mods**, **Datapacks** or **Modpacks**. 4. Type a name in the search field, such as `LuckPerms`, and select **Search**. Each result shows the addon's name, its kind, a short description, the source it comes from, its author and how many times it has been downloaded. For plugins, mods, datapacks and resource packs, the results list only projects with a build for your server's software and Minecraft version. A Paper server also finds plugins published for Spigot and Bukkit, and a Quilt server finds Fabric mods. Modpack results are not narrowed, because a modpack brings its own loader and Minecraft version. ## Install an addon 1. Find the addon, then select **Install…** on its result. 2. Choose a build in **Version**. A build the source does not let us download shows `(not installable)` and cannot be chosen. 3. To see what the build needs, select **Dependencies**. [Check dependencies](#check-dependencies) explains the list. 4. Select **Install**. > [!NOTE] > The **Version** list shows the builds the source publishes for every loader and Minecraft version, and it selects the first one. Check that the build you choose names your server's software and Minecraft version. The install job refuses a build made for anything else. A message confirms that the install is queued, and the job appears in the jobs card at the top of the **Software** tab. **Project page** opens the addon's page on its source, where you can read its instructions. The file goes into the folder the server loads that kind of addon from: | Kind | Folder | | --- | --- | | Plugin | `plugins` | | Mod | `mods` | | Datapack | The world's `datapacks` folder, such as `world/datapacks`. It follows `level-name` in `server.properties`. | The install does not stop or restart the server. Restart it from the [console](/docs/managed-containers/console/) to load the new addon. Plugin, mod and datapack installs can run side by side. A modpack, a software change or anything that deletes files waits for earlier jobs to finish and then runs alone. While a job waits, it shows `Waiting`, and the panel says `Another software job is running; this one queues behind it.` ## Check dependencies Select **Dependencies** next to **Install** to list the projects the chosen build names. Each row has a badge: `Required` : The addon does not work without it. `Optional` : The addon works without it and uses it when it is there. `Incompatible` : The addon does not work alongside it. Next to each row, the state says `Installed`, `Installing` or `Not installed`. An incompatible project that is not on the server shows `Conflicts`. When an incompatible project shows `Installed`, remove it or choose another addon. `No dependencies: it runs on its own.` means the build names none. > [!WARNING] > When required dependencies are missing, the panel says the installer adds them with the addon, such as `2 required dependencies are not installed yet; the installer adds them with this one.` The dashboard installs only the addon you chose. Install each required dependency yourself from the Marketplace, or send them in `dependencies` [through the API](#with-the-api), which installs them for you. A required dependency that only runs in players' games is left out of that count, because the server does not need it. ## Install a modpack 1. On the **Marketplace** panel, choose **Modpacks** and search for the pack. 2. Select **Install…** and choose a version. A note names the loader and Minecraft version the pack runs on, such as `This pack runs on Fabric 1.21.1.`, and says whether we switch the server to them. 3. Select **Install**. The install then runs in this order: 1. The job stops the server. 2. It downloads the pack and adds the pack's server files beside the files already on the server. It merges the pack's `overrides` folder into the server's folders. It skips folders that only players' games read, such as `resourcepacks` and `shaderpacks`, and mods whose publisher says they only run in the game. 3. When the server does not already run the pack's loader and Minecraft version, a software change to them follows as a second job. It keeps your files and sets the server up to run the new loader, as [Change the server software](/docs/managed-containers/software/) describes. Neither job starts the server. Start it from the [console](/docs/managed-containers/console/) when both jobs show `Succeeded`. > [!WARNING] > Minecraft cannot load a world saved by a newer version. When the pack runs an older Minecraft version than your world was last saved with, the world does not load after the switch. The note on the panel warns you when the versions differ. The dashboard adds the pack on top of what is there, so mods from before stay in `mods` and can clash with the pack's. To start from an empty server, delete the old files in the [Files tab](/docs/managed-containers/files/) first, or install through the API with `mode` set to `replace`, which deletes every file on the server before the install. We do not track the mods in a pack one by one. They show as **Uploaded by hand** on the **Installed addons** panel, and we cannot check them for updates. The pack itself is listed under **Installed from the marketplace**, but it cannot be removed there: to move to another pack, install it through the API with `mode` set to `replace`. ## Resource packs and server software The kind list also offers **Resource packs**, but the Marketplace cannot install one. Players' games download a server resource pack from a link, so nothing goes onto the server. Set it on the [Resource pack](/docs/managed-containers/software/resource-pack/) panel with the pack's download link instead. On some servers the list offers `server_jar` too. Change the server's own software under [Server software](/docs/managed-containers/software/) instead, so the Java runtime and startup command change with it. ## Remove an install Everything the Marketplace installed is listed under **Installed from the marketplace**, below the search, with its version, kind, source, path and install date. 1. Select the bin icon at the end of the row. 2. Confirm in the dialog. We delete the files that install wrote and drop our record of it, straight away. The server keeps a removed plugin or mod loaded until it restarts. The addon's data folder, such as `plugins/LuckPerms`, stays: delete it in the [Files tab](/docs/managed-containers/files/) if you no longer need its settings. The [Installed addons](/docs/managed-containers/software/installed-addons/) panel removes addons too, and it also lists the files you uploaded yourself. ## Result The job shows `Queued`, then `Running` with stages such as `Resolving`, `Downloading` and `Finalizing`, then `Succeeded`. The addon is in its folder, and it is listed under **Installed from the marketplace** and on the **Installed addons** panel. It loads the next time the server starts. A modpack's job also shows `Stopping` and `Extracting`, and a software change job may follow it. ## Troubleshooting The addon does not load after a restart : Open **Dependencies** for its version and install every `Required` project that shows `Not installed`. The [console](/docs/managed-containers/console/) shows the error the server hit while loading it. `No version of this is compatible with the server.` : The source lists no builds for this project, or we could not reach the source. Try again later, or choose another addon. `Minecraft Paper loads plugins, datapacks and a server resource pack, not mods.` : The server's software cannot load that kind of addon. The message names your software and what it loads. Choose another kind, or [change the software](/docs/managed-containers/software/). `This build was published for a different mod loader than this server runs.` : The job failed because the build you chose is for other software. The message starts with the build's name and what your server runs. Install again and choose a build made for your server's software. `This build was not published for this server's Minecraft version.` : The build is for another Minecraft version. Install again and choose a build for your server's version. `This is a client-side mod. It runs in each player's game, not on the server, and on a server it does nothing or stops it from starting.` : The publisher says the project only runs in players' games. We never install it on a server. Players install it in their own game. `SpigotMC only allows the newest release to be downloaded automatically. Install the latest version, or upload the jar yourself in Files.` : The plugin comes from SpigotMC, which shows as the `Spiget` source. Install the newest version, or download the version you want and upload it in the [Files tab](/docs/managed-containers/files/). `This is a paid SpigotMC resource. Buy it on SpigotMC, then upload the jar in Files.` : We cannot download paid plugins for you. Upload the jar yourself. `This resource is hosted off-site. Open the project page, download the jar, then upload it in Files.` : The publisher hosts the file somewhere we cannot download from. Upload the jar yourself. `This release has no downloadable file attached.` : The publisher uploaded no file for that build. Choose another version. `That version no longer exists on the source.` : The publisher removed the build. Choose another version. `This source is temporarily unavailable. Try again shortly.` : The source did not answer. Select **Try again** on the job later. `The download did not finish in time. Try again.` : Select **Try again** on the job. `This modpack does not publish a server pack, so it cannot be installed on a server automatically. Look for a version with a server pack, or set it up by hand in Files.` : A CurseForge modpack installs only from a version that publishes a server pack. Choose another version, or another pack. `The author has not allowed this file to be installed by third parties. Download it from CurseForge and upload it in Files.` : Download the file from CurseForge and upload it in the [Files tab](/docs/managed-containers/files/). `A server resource pack is not downloaded onto the server; players fetch it from a URL. Apply it with the resource pack tool instead.` : Resource packs are set with a link. Use the [Resource pack](/docs/managed-containers/software/resource-pack/) panel. `Modpacks cannot be uninstalled file-by-file; install another modpack with mode=replace instead` : A modpack's files mix with yours, so we cannot tell which ones to delete. Install another pack through the API with `mode` set to `replace`, or delete the files in the [Files tab](/docs/managed-containers/files/). `Cannot uninstall while a replace, wipe, or software change is in progress. Wait for it to finish or cancel it first.` : A job that replaces the server's files is running. Wait for it, or select **Cancel job** on it, then remove the addon. `Too many requests for this action. Please wait and try again.` : A server accepts 8 software actions from one account in 10 minutes, and each install counts. Searching does not. The response says how many seconds to wait. ## Related - [Manage installed addons](/docs/managed-containers/software/installed-addons/) - [Install server updates](/docs/managed-containers/updates/) - [Set a server resource pack](/docs/managed-containers/software/resource-pack/) - [Change the server software](/docs/managed-containers/software/) - [Manage files](/docs/managed-containers/files/) ## With the API The paths below are relative to `/api/v1/client/servers/{uuid}`. `GET /software/sources` lists the sources you can search. Add `?kind=plugin`, or another kind, for the sources that carry it. Each source has a `uuid`, `slug`, `name`, `adapter_type`, `supported_kinds`, `sort_options`, `supports_search` and `configured`. ### Search ```bash curl -X POST https://api.coritan.com/api/v1/client/servers/$SERVER/software/search \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"query": "luckperms", "kind": "plugin"}' ``` | Field | What it does | | --- | --- | | `query` | The name to search for. | | `kind` | `plugin`, `mod`, `modpack`, `datapack`, `resourcepack`, `server_jar`, `bedrock_addon` or `bedrock_resource_pack`. With a kind, the search fits the server's software and Minecraft version, as in the dashboard. | | `page` | The page, from 1. | | `limit` | Results per source and page, 1–100. The default is 20. | | `source_uuid` | Search one source only. | | `sort` | One of the source's `sort_options` values. | | `loaders`, `game_versions` | Narrow the search further. You can narrow within what the server runs but not go outside it. | ```json { "items": [ { "identifier": "Vebnzrzj", "name": "LuckPerms", "kind": "plugin", "description": "A permissions plugin for Minecraft servers.", "downloads": 1520000, "project_url": "https://modrinth.com/plugin/luckperms", "source_uuid": "0f7c2a4e-5b1d-4c3a-9e8f-2d6b1a7c9e30", "source_name": "Modrinth", "adapter_type": "modrinth" } ], "errors": [], "page": 1, "limit": 20, "total": 1, "has_more": false, "scope": { "loader": "paper", "game_version": "1.21.4", "loaders": ["paper", "spigot", "bukkit", "purpur", "folia"], "game_versions": ["1.21.4"] } } ``` A result also carries `slug`, `author`, `icon_url`, `loaders` and `game_versions`. `errors` names each source that failed, with its `source_name` and `message`, while the others still answer. `sources` counts the results from each source, and `scope` says what the search was narrowed to. A kind the server cannot load returns no results and the reason in `unsupported_kind`. ### Versions and dependencies `GET /software/versions/{source_uuid}/{identifier}?kind=plugin` returns `versions`. With `kind` set to `plugin`, `mod`, `datapack` or `resourcepack`, the list holds only the builds that fit the server, and each row says so in `compatible`. Add `include_incompatible=true` to list every build, with `incompatible_reason` set to `incompatible_loader` or `incompatible_game_version` where it does not fit. Each row has a `version_id` to install, a `name`, `release_date`, `game_versions`, `loaders`, `version_type` and `installable`, with `reason_text` when a build cannot be installed. `page` and `limit` (1–200, default 50) page through the list. `GET /software/dependencies/{source_uuid}/{identifier}?version=latest&kind=plugin` resolves a build's dependencies. Pass a `version_id` in `version`, or `latest` for the build an install would choose. The response has `dependencies` (required and optional), `incompatible`, `embedded` and `missing_required`. Each row has the project's `identifier`, `name`, `dependency_type`, `client_only`, `installed` and `installing`, and `record_uuid` and `installed_version` when it is on the server. `missing_required` lists the identifiers of required dependencies that are not installed, not installing and not client-side: send it as `dependencies` when you install. ### Install ```bash curl -X POST https://api.coritan.com/api/v1/client/servers/$SERVER/software/install \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"source_uuid": "0f7c2a4e-5b1d-4c3a-9e8f-2d6b1a7c9e30", "identifier": "Vebnzrzj", "kind": "plugin"}' ``` | Field | What it does | | --- | --- | | `source_uuid` | Required. The source, from the search results. | | `identifier` | Required. The project, from the search results. | | `kind` | Required. `plugin`, `mod`, `modpack`, `datapack`, `server_jar`, `bedrock_addon` or `bedrock_resource_pack`. | | `version` | A `version_id`. The default, `latest`, installs the newest release that fits the server, or the newest build when the project publishes no releases. | | `mode` | `add`, the default, installs beside what is there. `replace` deletes every file on the server first and needs `confirm_server_name`. `update` replaces the build of a project we already track. A modpack takes `add` or `replace`. | | `confirm_server_name` | The server's name, to confirm `replace`. Case does not matter. | | `stop_server` | `true` stops the server before the install. `replace` and modpacks always stop it. | | `display_name` | The name the jobs list shows. | | `dependencies` | Up to 25 identifiers from the same source to install with it, for plugins, mods and datapacks. Each gets its own job at the newest build that fits. We skip those already installed and those that only run in players' games. | | `allow_incompatible` | `true` installs a build made for another loader or Minecraft version. Client-side mods are refused whatever you send. | The response is the install job, with its `uuid`, `kind`, `mode`, `status`, `stage`, `progress` and `requested_version`. When you sent `dependencies`, `dependency_installs` lists their jobs. Follow a job with `GET /software/installs/{install_uuid}`, as [Change the server software](/docs/managed-containers/software/#with-the-api) explains. Sending the same project while its install is queued or running returns that job instead of a new one. `GET /software` lists every install we track on the server, newest first. Each record has a `uuid` to pass as `record_uuid`, with `source_uuid`, `identifier`, `kind`, `current_version`, `version_name`, `install_path`, `display_name`, `source_name` and `installed_at`. `DELETE /software/{record_uuid}` deletes the files the install wrote and the record. It answers `{"deleted": true, "uuid": "…", "identifier": "…"}`, and it does not stop the server. | Status | When | | --- | --- | | `400` | A field is missing or wrong, such as `source_uuid and identifier are required`, `kind is required (plugin, mod, modpack, datapack, server_jar, bedrock_addon or bedrock_resource_pack)`, `mode must be one of: add, replace, update` or `at most 25 dependencies can be installed at once`. A `replace` without the server's name answers `Type the server name to confirm deleting every file on the server`. The server's software cannot load the kind, or the kind is a resource pack. | | `404` | The record does not exist: `Installed software not found`. | | `409` | Removing a modpack, or removing while a job that replaces the server's files is running. | | `429` | More than 8 software actions from your account on this server in 10 minutes. | On a server shared with you, listing sources, versions, dependencies and installs needs `software.read`. Searching needs `software.search`, installing needs `software.install` and removing needs `software.delete`. ## API - `GET /api/v1/client/servers/{uuid}/software/sources`: List enabled software sources for a kind (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-get-api-v1-client-servers-uuid-software-sources) - `POST /api/v1/client/servers/{uuid}/software/search`: Search available software (JSON body preferred; query params kept for back-compat) (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-post-api-v1-client-servers-uuid-software-search) - `GET /api/v1/client/servers/{uuid}/software/versions/{source_uuid}/{identifier}`: Get available versions for software (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-get-api-v1-client-servers-uuid-software-versions-source-uuid-identifier) - `GET /api/v1/client/servers/{uuid}/software/dependencies/{source_uuid}/{identifier}`: What a version requires, named, with what is already installed marked (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-get-api-v1-client-servers-uuid-software-dependencies-source-uuid-identifier) - `POST /api/v1/client/servers/{uuid}/software/install`: Queue a software install job (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-post-api-v1-client-servers-uuid-software-install) - `GET /api/v1/client/servers/{uuid}/software`: List installed software records for a server (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-get-api-v1-client-servers-uuid-software) - `DELETE /api/v1/client/servers/{uuid}/software/{record_uuid}`: Uninstall a tracked software artifact (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-delete-api-v1-client-servers-uuid-software-record-uuid) # Manage installed addons > Turn plugins, mods and datapacks on or off, update them to newer builds and remove them from your server. Source: https://www.coritan.com/docs/managed-containers/software/installed-addons/ In the dashboard: - /dashboard/servers/…/software: https://www.coritan.com/dashboard/servers The **Installed addons** panel on a server's **Software** tab lists every plugin, mod and datapack in the folders your server loads from. It shows the ones you installed from the [Marketplace](/docs/managed-containers/software/plugins-and-mods/) and the ones you uploaded yourself. From the panel you can turn an addon off without deleting it, update it to a newer build and remove it. The panel is there for Java software. A Bedrock server manages its content on the **Packs** panel instead: see [Manage Bedrock packs](/docs/managed-containers/software/bedrock-packs/). ## Before you begin - The server must be installed, because the panel reads the files on it. - On a server someone shared with you, the Install software permission lets you see the list, turn addons on or off and update them. Removing an addon or forgetting a record needs `software.delete`, which the dashboard's permission list does not offer, so the owner can grant it only [through the API](/docs/managed-containers/users/#with-the-api). The folders the panel reads depend on the server's software: | Software | Folders | | --- | --- | | Plugin servers, such as Paper, Purpur and Spigot | `plugins` and the world's `datapacks` | | Mod loaders: Fabric, Quilt, Forge and NeoForge | `mods` and the world's `datapacks` | | Arclight, Mohist and Magma, which load both | `plugins`, `mods` and the world's `datapacks` | | Vanilla | The world's `datapacks` | | Proxies, such as Velocity and BungeeCord | `plugins` | The world's datapack folder follows `level-name` in `server.properties`: `world/datapacks` by default, or `survival/datapacks` for a world called `survival`. ## Read the list 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the server, then the **Software** tab. 2. Select **Installed addons**. Each kind of addon has a card, **Plugins**, **Mods** or **Datapacks**, that names its folder and counts the files in it. Addons that are on come first, in name order, then the ones that are off. Each row shows the addon's name, the version when the Marketplace installed it, its path, its size and when it last changed. The panel lists only files the server loads: `.jar` files for plugins and mods, and `.zip` files and folders for datapacks. A plugin's own data folder, such as `plugins/LuckPerms`, and any other file in the folder are left out. Manage those in the [Files tab](/docs/managed-containers/files/). A badge on a row tells you more: **Disabled** : The file name ends in `.disabled`, so the server does not load it. **Uploaded by hand** : The Marketplace did not install this file. Files you upload or copy in yourself, over [SFTP](/docs/managed-containers/sftp/) or an [import](/docs/managed-containers/import/), show this badge, and so do the mods a modpack brings. We cannot check them for updates. **Update available** : A newer build fits your server. The badge names the version when it can, such as `Update to 2.11.0`. **Files missing** : The Marketplace installed this addon, but its file is no longer in the folder. Someone deleted or renamed it outside this panel. The bar above the cards counts the updates, such as `2 updates available`. The refresh button next to it reads the folders again. ## Turn an addon off or on 1. On the **Installed addons** panel, find the addon's row. 2. Turn off the switch at the end of the row. Turn it on again to switch the addon back on. We rename the file: `EssentialsX-2.20.1.jar` becomes `EssentialsX-2.20.1.jar.disabled`, and the server skips it. A running server keeps what it loaded, so the change applies the next time the server starts. Restart it from the [console](/docs/managed-containers/console/) when you are ready. A datapack folder has no switch, because the game loads a folder with a `pack.mcmeta` whatever it is called. To be able to switch it off, replace the folder with the datapack's `.zip` file. ## Update an addon We check the addons the Marketplace installed against their source. An update is a build that fits your server's software and Minecraft version and is newer than the one on disk. We prefer release builds over beta and alpha builds when the source has both. 1. On the **Installed addons** panel, find a row with an update badge. 2. Select **Update**. We queue an install job, which appears in the jobs card at the top of the **Software** tab. The job removes the old build's file and downloads the new build in its place. An addon you had turned off stays off after the update. The new build loads the next time the server starts. To update several addons at once, or to see every update across the server, use the [Updates tab](/docs/managed-containers/updates/). ## Remove an addon 1. On the **Installed addons** panel, select the bin icon at the end of the addon's row. 2. Select **Remove addon**. We delete the file. For an addon the Marketplace installed, we delete every file that install wrote and drop our record of it. The addon's data folder, such as `plugins/EssentialsX`, stays: delete it in the [Files tab](/docs/managed-containers/files/) if you no longer need its settings. Removing does not stop the server. A running server keeps the addon loaded until it restarts. ## Forget a missing addon A row with the **Files missing** badge has a **Forget** button in place of the switch. Select **Forget** to drop our record of the install. Nothing on the server changes, and the row disappears from the list. ## Result A message confirms each change, such as `EssentialsX turned off for the next start.`, `EssentialsX removed.`, `Record dropped.` or `Updating to 2.11.0.` An update's job shows as `Queued`, then `Running` with its progress, then `Succeeded` or `Failed`. ## Troubleshooting **No addon folders** : The server's software loads no plugins, mods or datapacks, as with some proxies. To add some, [change the software](/docs/managed-containers/software/). `Could not read the addon folders. Check that the server is installed.` : We could not list the server's files. Check that the server finished installing and is not suspended, then try again. `A datapack folder loads whatever it is named; delete it or zip it to be able to switch it off` : Datapack folders cannot be switched off by renaming. Remove the folder, or replace it with the datapack's `.zip` file. `EssentialsX-2.20.1.jar.disabled already exists; remove one copy first` : The folder holds the same file both on and off. Remove one of the two copies. `That file is no longer on the server` : Someone moved or deleted the file since the list loaded. Select the refresh button to read the folder again. `Cannot uninstall while a replace, wipe, or software change is in progress. Wait for it to finish or cancel it first.` : A job that replaces the server's files is running. Wait for it in the jobs card, or select **Cancel job** there, then remove the addon. `That software is currently being installed. Cancel the install first, or wait for it to finish.` : The same addon has an install or update job running. Let it finish, or cancel it, then try again. `Could not delete installed files under /plugins: …` : The server refused the deletion. We keep the record so you can try again, or delete the files in the [Files tab](/docs/managed-containers/files/). `This record has no removable files; delete them in the file manager instead` : Our record of the install does not name its files. Delete them in the [Files tab](/docs/managed-containers/files/), then select **Forget** on the row, or refresh the list. The update job fails : The job's row in the jobs card shows the reason. Select **Try again** to queue the same update again. A source that no longer offers the build fails with a message from that source. `Too many requests for this action. Please wait and try again.` : A server accepts 8 software actions from one account in 10 minutes. Updates count towards that, along with installs and software changes. The response says how many seconds to wait. ## Related - [Install plugins, mods and modpacks](/docs/managed-containers/software/plugins-and-mods/) - [Install server updates](/docs/managed-containers/updates/) - [Start a server in safe mode](/docs/managed-containers/software/safe-mode/) - [Manage files](/docs/managed-containers/files/) ## With the API `GET /api/v1/client/servers/{uuid}/software/addons` returns the inventory for every addon kind the server loads. Add `?kind=plugin`, `mod` or `datapack` for one kind. Under `kinds`, each kind has its `directory`, its `items` and the `missing` records whose files are gone. `supported_kinds` lists the kinds the server loads. ```json { "kinds": { "plugin": { "kind": "plugin", "directory": "/plugins", "items": [ { "kind": "plugin", "name": "LuckPerms", "filename": "LuckPerms-Bukkit-5.4.137.jar", "path": "/plugins/LuckPerms-Bukkit-5.4.137.jar", "directory": false, "size_bytes": 1432118, "enabled": true, "can_toggle": true, "tracked": true, "missing": false, "record_uuid": "5b1f7a0e-2c4d-4e8a-9b3f-6d2a1c0e7f45", "version_name": "5.4.137" } ], "missing": [] } }, "supported_kinds": ["plugin", "datapack"] } ``` An item also carries `modified_at`, and for a tracked addon `source_uuid`, `source_name`, `identifier`, `display_name`, `version`, `icon_url` and `project_url`. An untracked item has `tracked` set to `false` and `null` in those fields. Asking for a kind the server does not load returns empty `kinds` and names it in `unsupported`. Turn an addon off, or on with `"enabled": true`: ```bash curl -X POST https://api.coritan.com/api/v1/client/servers/$SERVER/software/addons/toggle \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"path": "/plugins/EssentialsX-2.20.1.jar", "enabled": false}' ``` The response gives the new `path`, the `previous_path`, `enabled`, `changed` and `kind`. A file that is already in the state you asked for answers with `changed` set to `false`. The path must name a file directly inside one of the server's addon folders; any other path answers `400` with `That path is not inside an addon folder on this server`. | Request | What it does | | --- | --- | | `POST /software/addons/remove` | Deletes the addon at `{"path": "…"}`. A tracked addon goes through the uninstall and the response has `deleted`, `uuid`, `identifier`, `path`, `kind` and `tracked`. | | `DELETE /software/addons/records/{record_uuid}` | Drops the record and leaves the files alone. Returns `{"forgotten": true, "uuid": "…"}`. The file, if it is still there, then lists as uploaded by hand. | | `GET /software/updates` | Checks the tracked addons for updates. Returns `updates` and `available`, the number with an update. Add `kind` for one kind, and `include_current=true` to list addons already on the newest build too. | | `POST /software/{record_uuid}/update` | Queues the update. Send `{"version": "…"}` with a version id from `latest_version`, or leave the body out for the newest build. Returns the install job. | The paths are relative to `/api/v1/client/servers/{uuid}`. The [Updates tab page](/docs/managed-containers/updates/#with-the-api) describes each entry in `updates`. Server software and modpacks are never listed there, and updating one answers `400`: change them from [Server software](/docs/managed-containers/software/) instead. The install job has a `uuid`, `kind`, `mode` (`update`), `status`, `stage`, `progress` from 0 to 100, `requested_version`, `resolved_version` once known, and `error` when it fails. Follow it with `GET /software/installs/{install_uuid}`, as [Change the server software](/docs/managed-containers/software/#with-the-api) explains. Sending the same update while one is queued or running returns the job already in progress. A missing record answers `404` with `Installed software not found`. A job that blocks the change answers `409` with the messages under [Troubleshooting](#troubleshooting). Any other refusal answers `400`. On a server shared with you, listing and checking for updates need `software.read`, toggling and updating need `software.install`, and removing and forgetting need `software.delete`. ## API - `GET /api/v1/client/servers/{uuid}/software/addons`: Every addon in the folders the server loads from, tracked or hand-uploaded (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-get-api-v1-client-servers-uuid-software-addons) - `DELETE /api/v1/client/servers/{uuid}/software/addons/records/{record_uuid}`: Drop a tracking record whose files are gone, without touching the disk (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-delete-api-v1-client-servers-uuid-software-addons-records-record-uuid) - `POST /api/v1/client/servers/{uuid}/software/addons/remove`: Delete one addon from disk, through the tracked uninstall when it has a record (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-post-api-v1-client-servers-uuid-software-addons-remove) - `POST /api/v1/client/servers/{uuid}/software/addons/toggle`: Switch one addon file on or off (Foo.jar <- Foo.jar.disabled) (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-post-api-v1-client-servers-uuid-software-addons-toggle) - `GET /api/v1/client/servers/{uuid}/software/updates`: Newer compatible builds for the addons the marketplace installed (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-get-api-v1-client-servers-uuid-software-updates) - `POST /api/v1/client/servers/{uuid}/software/{record_uuid}/update`: Queue an in-place update: the old file is removed, the new build installed (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-post-api-v1-client-servers-uuid-software-record-uuid-update) # Change Java settings > Set the Java memory, startup flags and Java version your Minecraft Java server starts with. Source: https://www.coritan.com/docs/managed-containers/software/java-settings/ In the dashboard: - /dashboard/servers/…/software: https://www.coritan.com/dashboard/servers The **Java** panel on a server's **Software** tab sets three things a Java server starts with: how much memory Java may use for the game (the *heap*), the startup flags that tune Java's garbage collector, and the Java version. Saving never restarts the server. Your changes apply the next time it starts. ## Before you begin - The panel is there for servers that run on Java. A Bedrock server does not have it. - On a server someone shared with you, reading the panel needs `startup.read` and saving needs `startup.update`. The dashboard's permission list offers neither, so the owner can grant them only [through the API](/docs/managed-containers/users/#with-the-api). - Each save counts towards the limit of 8 software actions from one account on a server in 10 minutes. To open the panel: 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the server, then the **Software** tab. 2. Select **Java**. The **Java settings** card shows the Java version the server runs, such as `Java 21`, next to its title. ## Set the Java memory Java needs memory beyond the heap, and the server is stopped if it runs out of memory altogether. So the heap starts below your plan's memory, and you can raise it: | Setting | Value | | --- | --- | | Default on a paid plan | 80% of the plan's memory, rounded down to 16 MB | | Default on a free server | 2048 MB | | Lowest | Half the plan's memory, rounded down to 256 MB, and never less than 1024 MB | | Highest | The plan's memory | For example, on a plan with 4096 MB the default is 3264 MB, and the heap can go from 2048 MB to 4096 MB. 1. Under **Java memory**, move the slider. It moves in steps of 256 MB, and it also stops at the default and at the full plan. 2. Or select the button under the slider that uses the default or the full plan. 3. Or type a number of megabytes in **Exact size**. 4. Select **Save Java settings**. A badge next to **Java memory** says `Default`, `Full plan` or `Custom`. > [!WARNING] > With the heap at the full plan, Java has little room left for everything else it needs, and a busy server can run out of memory and be stopped. If that happens, go back to the default. When your plan's memory changes, or a free server moves to a paid plan, the heap goes back to the new plan's default. When the heap reaches 12 GB, four of the startup flags that size the young generation switch to larger values. The panel says so under **Exact size**. ## Choose the startup flags The startup flags are Aikar's flags, a set widely used to keep Minecraft's garbage collection smooth. All of them are on by default. You can turn off the ones you do not want, but you cannot add flags of your own. 1. Under **Startup flags**, choose **Recommended** for every flag, **Off** for none, or **Custom**. 2. To see or change single flags, select **Show flags**. Ticking or clearing a flag switches to **Custom**. 3. Select **Save Java settings**. Next to the choice, the panel counts the flags that are on, such as `19 of 19 flags on · Java 21`. A flag that your Java version does not accept is greyed out, with the reason, such as `Needs Java 16 or newer; this server runs Java 11.` *Unlock experimental options* is added automatically while an experimental flag is on. Startup flags are there for Minecraft Java software only. On other Java software the panel says `Startup flags are offered to Minecraft Java software only.` ## Change the Java version 1. Choose a Java in **Java version**. The list puts the newest first and marks the current one `(current)`. Next to each Java it names the Minecraft releases that Java is for. 2. Select **Save Java settings**. | Java | Minecraft releases | | --- | --- | | Java 21 and newer | 1.20.5 and newer | | Java 17 | 1.17 to 1.20.4 | | Java 8 | 1.16.5 and older | The server does not start on the wrong Java. When you change the Minecraft version under [Server software](/docs/managed-containers/software/), choose the Java there in the same step. ## See the startup command Select **Show startup command** to see the full command the next start runs, with the heap and flags in place. Select **Copy startup command** to copy it. ## Reset the heap and flags **Reset to defaults…** appears once you have changed the heap or the flags. 1. Select **Reset to defaults…**. 2. Select **Reset Java settings**. The heap and the flags go back to the plan's defaults on the next start. The Java version stays as it is. ## Result A message says `Java settings saved. They apply on the next start.`, or `Heap and startup flags reset to their defaults. They apply on the next start.` after a reset. Restart the server from the [console](/docs/managed-containers/console/) to use them. ## Troubleshooting **No Java settings here** : The server cannot use the panel. `This server does not run on Java, so there is no heap or startup flags to set.` means it runs other software. `The startup command sets its own heap; ask support to change it.` means its startup command names a fixed heap. Ask [support](/docs/support/) to change it. **Could not load the Java settings** : On a server shared with you, you need `startup.read`. Otherwise, select **Try again**. `Enter a whole number of megabytes from 2048 to 4096.` : The number in **Exact size** is outside what your plan allows. The message gives your plan's range. **Could not save the Java settings** : The panel shows the reason, such as a flag your Java version does not accept. Change that setting and save again. The server does not start after a change : Check that the Java version fits the Minecraft version, using the table above. Then check the [console](/docs/managed-containers/console/) for the error. The server stops because it ran out of memory : Lower the heap to the default, or [move to a larger plan](/docs/billing/change-plan/). `Too many requests for this action. Please wait and try again.` : A server accepts 8 software actions from one account in 10 minutes. The response says how many seconds to wait. ## Related - [Change the server software](/docs/managed-containers/software/) - [Use the console and power controls](/docs/managed-containers/console/) - [Server settings](/docs/managed-containers/settings/) ## With the API `GET /api/v1/client/servers/{uuid}/software/jvm` returns what the panel shows: `supported` : `false` when the server cannot use the panel, with the reason in `unsupported_reason`. `memory` : `supported` (whether the heap can change), `heap_mb`, `default_mb`, `min_mb`, `max_mb`, `stops` (the slider's positions), `is_default`, `free` and `size_tier` (`small`, or `large` from 12 GB). `flags` : `supported`, `java_major`, `selected` (the ids that are on), `defaults`, `is_default` and `catalog`. Each catalog entry has an `id`, `label`, `description`, `group`, `flag` (what it adds to the command), `available`, `unavailable_reason`, `experimental` and `selected`. `runtime` : `supported`, the current `slug` and `java`, and `options`, each with a `slug`, `java` and `name`. `effective_command` : The startup command the next start runs. `PATCH` the same path with any of these fields: | Field | What it does | | --- | --- | | `heap_mb` | The heap, a whole number of megabytes from `min_mb` to `max_mb`. | | `flags` | The ids of the flags to turn on, from `catalog`. `[]` turns them all off. | | `runtime_template_slug` | A `slug` from `runtime.options`, such as `java-21`. | | `reset` | `true` drops the stored heap and flags, as **Reset to defaults…** does. The other fields are then ignored. | ```bash curl -X PATCH https://api.coritan.com/api/v1/client/servers/$SERVER/software/jvm \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"heap_mb": 3072, "flags": ["g1gc", "parallel-ref-proc", "max-gc-pause", "disable-explicit-gc"]}' ``` The response is the new view, as `GET` returns it, plus `synced`, which says whether we could pass the new settings on straight away. When it is `false`, they still apply at the next start. To go back to the recommended flags but keep your heap, send the `defaults` list as `flags`. | Flag id | Label | Adds | | --- | --- | --- | | `vector-api` | Vector API (SIMD) | `--add-modules=jdk.incubator.vector`, on Java 16 and newer | | `g1gc` | G1 garbage collector | `-XX:+UseG1GC` | | `parallel-ref-proc` | Parallel reference processing | `-XX:+ParallelRefProcEnabled` | | `max-gc-pause` | Pause target 200 ms | `-XX:MaxGCPauseMillis=200` | | `unlock-experimental` | Unlock experimental options | `-XX:+UnlockExperimentalVMOptions` | | `disable-explicit-gc` | Ignore System.gc() | `-XX:+DisableExplicitGC` | | `g1-heap-waste` | Heap waste 5% | `-XX:G1HeapWastePercent=5` | | `g1-mixed-gc-count` | Mixed collection target 4 | `-XX:G1MixedGCCountTarget=4` | | `ihop` | Start marking at 15% | `-XX:InitiatingHeapOccupancyPercent=15`, on Java 28 and older | | `g1-mixed-live-threshold` | Mixed collection live threshold 90% | `-XX:G1MixedGCLiveThresholdPercent=90` | | `g1-rset-updating` | Remembered-set updating 5% | `-XX:G1RSetUpdatingPauseTimePercent=5` | | `survivor-ratio` | Survivor ratio 32 | `-XX:SurvivorRatio=32` | | `perf-disable-shared-mem` | No perf shared memory | `-XX:+PerfDisableSharedMem` | | `max-tenuring` | Tenuring threshold 1 | `-XX:MaxTenuringThreshold=1` | | `aikar-marker` | Aikar's flags marker | `-Dusing.aikars.flags=https://mcflags.emc.gs -Daikars.new.flags=true` | | `g1-new-size` | Young generation minimum | `-XX:G1NewSizePercent=30`, or `40` from 12 GB | | `g1-max-new-size` | Young generation maximum | `-XX:G1MaxNewSizePercent=40`, or `50` from 12 GB | | `g1-heap-region` | Heap region size | `-XX:G1HeapRegionSize=8M`, or `16M` from 12 GB | | `g1-reserve` | Heap reserve | `-XX:G1ReservePercent=20`, or `15` from 12 GB | `g1-mixed-live-threshold`, `g1-new-size` and `g1-max-new-size` are experimental: when any of them is on, `unlock-experimental` is added for you. A flag the startup command already carries is not added twice. A request the server cannot take answers `400` with the reason, such as `Send heap_mb, flags, runtime_template_slug or reset`, `heap_mb must be between 2048 and 4096 MB on this plan`, `Unknown startup flag 'xyz'; only the listed flags can be used`, `Vector API (SIMD): Needs Java 16 or newer; this server runs Java 11.` or `No Java runtime called 'java-99' is available`. A server that is not on a Java runtime answers `This server does not run on a Java runtime; changing it is a software change`: change it under [Server software](/docs/managed-containers/software/#with-the-api) instead. Reading needs `startup.read` and changing needs `startup.update`. ## API - `GET /api/v1/client/servers/{uuid}/software/jvm`: The heap slider and the allowlisted startup flags, as the panel shows them (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-get-api-v1-client-servers-uuid-software-jvm) - `PATCH /api/v1/client/servers/{uuid}/software/jvm`: Move the heap, pick startup flags from the catalog, or reset both (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-patch-api-v1-client-servers-uuid-software-jvm) # Set a server resource pack > Offer players a resource pack when they join your Minecraft Java server, and make it required if you want. Source: https://www.coritan.com/docs/managed-containers/software/resource-pack/ In the dashboard: - /dashboard/servers/…/software: https://www.coritan.com/dashboard/servers A *server resource pack* changes how the game looks and sounds for everyone on your server. The pack does not go onto the server. The server gives each player a link when they join, and their game downloads the pack from that link. The **Resource pack** panel on a server's **Software** tab writes that link, and the settings that go with it, into the server's `server.properties`. ## Before you begin - Put the pack's `.zip` file where players can download it, such as your own website. A pack published on Modrinth already has a download link. The link must download the file itself: a page that shows the file, as many file-sharing sites do, does not work. - The panel is there for Minecraft Java servers. A proxy passes players on to other servers, so set the pack on those servers instead. A Bedrock server keeps its packs in its own folders: see [Manage Bedrock packs](/docs/managed-containers/software/bedrock-packs/). - On a server someone shared with you, the Install software permission lets you set the pack. Removing it needs `software.delete`, which the dashboard's permission list does not offer, so the owner can grant it only [through the API](/docs/managed-containers/users/#with-the-api). ## Set the pack 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the server, then the **Software** tab. 2. Select **Resource pack**. 3. In **Pack URL**, paste the link to the pack's `.zip` file, such as `https://example.com/pack.zip`. It must start with `http://` or `https://`. 4. Optionally, fill in **SHA-1** with the pack file's SHA-1 checksum, 40 characters from `0` to `9` and `a` to `f`. With it, a player's game skips the download when it already has this pack, and checks the file it downloads. 5. Optionally, type a message in **Prompt**. Players see it when the game asks them to accept the pack. 6. To turn away players who decline the pack, tick **Required**. 7. Select **Save resource pack**. 8. Restart the server from the [console](/docs/managed-containers/console/), so it reads the new settings. To get the SHA-1 of the file on your computer, run `sha1sum pack.zip` on Linux, `shasum -a 1 pack.zip` on macOS or `certutil -hashfile pack.zip SHA1` on Windows. Every time you change the pack, its SHA-1 changes too, so update it here. > [!NOTE] > Players already online keep the pack they have. They get the new one the next time they join. ## Change or remove the pack To change the pack, edit the fields and select **Save resource pack** again, then restart the server. To stop offering a pack: 1. On the **Resource pack** panel, select **Remove pack…**. 2. Select **Remove resource pack**. We clear the pack's settings in `server.properties` and delete nothing else. Restart the server to stop offering the pack. ## What we write The panel writes these keys in `server.properties`: | Key | Value | | --- | --- | | `resource-pack` | The pack's link. | | `resource-pack-sha1` | The SHA-1, or empty. | | `require-resource-pack` | `true` when you ticked **Required**, otherwise `false`. | | `resource-pack-prompt` | Your prompt, written as a Minecraft text component such as `{"text": "This server uses a custom pack"}`. | | `resource-pack-id` | An id we make from the link. It stays the same while the link does, so games that have the pack do not download it again. | You can edit these keys yourself in the [Files tab](/docs/managed-containers/files/), and the panel reads them back. ## Result A message says `Resource pack set. Players get it on their next join. A running server must restart to reread server.properties.` The badge on the **Server resource pack** card changes to **Set**. After a restart, players who join see the game's prompt to download the pack. After a removal, the message says `Resource pack removed.` and the badge shows **Not set**. ## Troubleshooting **No server resource pack here** : The server's software cannot offer a pack this way. The panel gives the reason, such as `A proxy has no world; set the resource pack on the backend servers.` `Could not read server.properties.` : We could not read the server's files. Check that the server is installed and not suspended, then open the panel again. `There is no server.properties yet. We create it, with the pack, the first time you save.` : The server has not started yet. Save the pack, and the game adds its other settings to the file when it first starts. `Players download the pack from their own computers, so the URL has to be public` : The link points at a private address, such as `localhost` or an address on your home network. Host the pack somewhere players can reach over the internet. `The SHA-1 must be 40 hexadecimal characters` : Copy the full checksum, or leave **SHA-1** empty. Players see missing or wrong textures : The pack was made for another Minecraft version. Use a build of the pack made for your server's version. Players download the pack every time they join : Add the pack's **SHA-1**. Players get an error when the game downloads the pack : The link does not lead straight to the `.zip` file, or the **SHA-1** does not match the file. Open the link in a browser: it should download the file. Then check the checksum again. The old prompt stays after you clear **Prompt** : When the field is empty, the dashboard leaves the prompt already in `server.properties`. Select **Remove pack…**, then set the pack again without a prompt. Or send `"prompt": ""` through the API. `This server has no server.properties yet; start it once first` : There is no pack to remove. Nothing needs doing. `Too many requests for this action. Please wait and try again.` : A server accepts 8 software actions from one account in 10 minutes, and saving a pack counts. The response says how many seconds to wait. ## Related - [Install plugins, mods and modpacks](/docs/managed-containers/software/plugins-and-mods/) - [Manage server files](/docs/managed-containers/files/) - [Use the console and power controls](/docs/managed-containers/console/) ## With the API `GET /api/v1/client/servers/{uuid}/software/resource-pack` returns the pack as `server.properties` declares it: `configured`, `url`, `sha1`, `required`, `prompt` (as plain text), `pack_id` and `host`. `supported` is `false` on software that cannot offer a pack, with the reason in `unsupported_reason`, and `properties_present` says whether `server.properties` exists. Set a pack from a link: ```bash curl -X PUT https://api.coritan.com/api/v1/client/servers/$SERVER/software/resource-pack \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/pack.zip", "sha1": "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12", "required": true, "prompt": "This server uses a custom pack"}' ``` Or name a resource pack from the [Marketplace](/docs/managed-containers/software/plugins-and-mods/#with-the-api) instead of a link: send `source_uuid`, `identifier` and optionally `version`, a `version_id`. We fill in the link and the SHA-1 that the source publishes. The default, `latest`, takes the newest build made for your server's Minecraft version, and a build made for another version is refused. | Field | What it does | | --- | --- | | `url` | The pack's link, `http://` or `https://`, up to 2048 characters. Required unless you send `source_uuid` and `identifier`. | | `sha1` | The file's SHA-1. Leave it out to clear it. | | `required` | `true` turns away players who decline. Leave it out to keep the current setting. | | `prompt` | The message players see, up to 1000 characters. Plain text, or a Minecraft text component in JSON. Leave it out to keep the current prompt, or send `""` to clear it. | | `source_uuid`, `identifier`, `version` | A marketplace resource pack to use instead of `url`. | The response is the new state with `restart_required` set to `true` and a `warnings` list, which says so when no SHA-1 was set. For a marketplace pack, `pick` gives the `url`, `sha1`, `filename`, `size_bytes` and `version` we used. `DELETE` the same path to stop offering the pack. It answers with the cleared state and `restart_required`. A request we refuse answers `400` with the reason, such as `Provide a url, or a source_uuid and identifier of a marketplace resource pack`, `The resource pack URL must start with http:// or https://`, `That URL is too long for server.properties`, `The prompt is too long` or `No version of this pack is published for Minecraft 1.21.4.` Reading needs `software.read`, setting needs `software.install` and removing needs `software.delete`. ## API - `GET /api/v1/client/servers/{uuid}/software/resource-pack`: The server resource pack as server.properties declares it (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-get-api-v1-client-servers-uuid-software-resource-pack) - `PUT /api/v1/client/servers/{uuid}/software/resource-pack`: Point players at a pack, from a URL or a marketplace resource pack version (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-put-api-v1-client-servers-uuid-software-resource-pack) - `DELETE /api/v1/client/servers/{uuid}/software/resource-pack`: Stop offering a server resource pack (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-delete-api-v1-client-servers-uuid-software-resource-pack) # Start a server in safe mode > Start a Minecraft Java world once with only the vanilla datapack, to recover from a datapack that stops it loading. Source: https://www.coritan.com/docs/managed-containers/software/safe-mode/ In the dashboard: - /dashboard/servers/…/software: https://www.coritan.com/dashboard/servers When a datapack fails to load, a Minecraft Java server stops during startup, and the [console](/docs/managed-containers/console/) shows: ```text Failed to load datapacks, can't proceed with server load. You can either fix your datapacks or reset to vanilla with --safeMode ``` *Safe mode* starts the server once with Minecraft's `--safeMode` option. The world loads with only the vanilla datapack, so the server comes up and you can fix or remove the datapack that broke it. The **Safe mode** panel on a server's **Software** tab adds the option for one start only: the next start after that is a normal one. ## Before you begin - Safe mode works on Minecraft Java 1.16 and newer, with plugin servers such as Paper, with Vanilla and with mod loaders such as Fabric and Forge. Proxies load no datapacks, and Bedrock servers have no safe mode. - Take a [snapshot](/docs/managed-containers/snapshots/) of the server first. Safe mode also skips the datapacks that were working, and you are about to change the world's files. - On a server someone shared with you, you need the Install software permission, and the Power permission to start the server from the panel. ## Start in safe mode now 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the server, then the **Software** tab. 2. Select **Safe mode**. 3. Select **Start in safe mode**. A running server restarts, and a stopped one starts. The badge on the panel shows **Running in safe mode** once the server is up. ## Arm safe mode for the next start To choose when the server starts, arm safe mode instead: 1. On the **Safe mode** panel, select **Arm for the next start**. The badge shows **Armed for next start**. 2. Start the server from the [console](/docs/managed-containers/console/) when you are ready, or select **Start now** on the panel. To cancel before the server starts, select **Disarm safe mode**. ## Fix the world and restart normally While the server runs in safe mode, the panel warns `The server is running in safe mode now. Fix or remove the datapack that broke it, then restart normally.` 1. Find the datapack that failed. The error lines in the [console](/docs/managed-containers/console/) before the message above name it. 2. Remove it, or replace it with a build that works with your Minecraft version. Datapacks are in the world's `datapacks` folder, such as `world/datapacks`. Remove them on the [Installed addons](/docs/managed-containers/software/installed-addons/) panel or in the [Files tab](/docs/managed-containers/files/). 3. Restart the server from the [console](/docs/managed-containers/console/). As soon as the safe-mode start stops, safe mode switches itself off, and the badge shows **Off**. If you restart without fixing the datapack, it can stop the server again. ## Result The server starts on the vanilla datapack alone, and the console shows the world loading. After the normal restart, the world loads with the datapacks that remain in its folder. ## Troubleshooting **Safe mode is not available here** : The server's software cannot use the option. The panel gives the reason, such as `Safe mode needs Minecraft 1.16 or newer.` or `A proxy loads no datapacks, so there is nothing for safe mode to skip.` On a Bedrock server, remove the broken pack as [Manage Bedrock packs](/docs/managed-containers/software/bedrock-packs/) describes. The panel says `Starting in safe mode.` but the server does not start : The start was refused, as it is while a free server waits in the queue to wake or while we have locked the server. Safe mode stays armed, so the server uses it the next time it starts. Start it from the [console](/docs/managed-containers/console/) when you can. The world still does not load in safe mode : Safe mode skips datapacks only. When the console shows another error, such as a broken mod or plugin, remove that instead. [Troubleshoot servers](/docs/managed-containers/troubleshooting/) lists common causes. The same error comes back after the normal restart : The broken datapack is still in the world's `datapacks` folder. Start in safe mode again and remove it. ## Related - [Manage installed addons](/docs/managed-containers/software/installed-addons/) - [Use the console and power controls](/docs/managed-containers/console/) - [Troubleshoot servers](/docs/managed-containers/troubleshooting/) ## With the API ```bash curl -X POST https://api.coritan.com/api/v1/client/servers/$SERVER/software/safe-mode \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"enabled": true, "start": true}' ``` | Field | What it does | | --- | --- | | `enabled` | Required. `true` arms safe mode for the next start. `false` disarms it. | | `start` | With `"enabled": true`, `true` also restarts a running server or starts a stopped one. | The response has `state` (`off`, `armed` or `active`), `armed`, `active`, `flag` (`--safeMode`) and `synced`. With `start`, `started` says whether the server started, `action` says whether we sent `start` or `restart`, and `start_error` gives the reason when the start was refused. Safe mode stays armed after a refused start. `GET /api/v1/client/servers/{uuid}/software/context` shows the same state under `safe_mode`, with `supported` and `unsupported_reason`. Arming on software that cannot use safe mode answers `400` with the reason, and a body without `enabled` answers `400` with `enabled is required`. Arming needs `software.install`, and `start` also needs `control.start`. ## API - `POST /api/v1/client/servers/{uuid}/software/safe-mode`: Arm a one-shot --safeMode start (vanilla datapack only), optionally starting now (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-post-api-v1-client-servers-uuid-software-safe-mode) # Let Bedrock players join a Java server > Turn on Bedrock crossplay so players on phones, consoles and Windows can join your Minecraft Java server. Source: https://www.coritan.com/docs/managed-containers/software/bedrock-crossplay/ In the dashboard: - /dashboard/servers/…/software: https://www.coritan.com/dashboard/servers Minecraft Bedrock Edition, the edition on phones, consoles and Windows, cannot join a Java server by itself. *Bedrock crossplay* adds two plugins to a Minecraft Java server: Geyser, which translates between the two editions, and Floodgate, which lets Bedrock players join without a Java account. You turn it on with one switch on the server's **Software** tab. We install both plugins, give Geyser a port of its own and keep both plugins up to date. ## Before you begin - The server must run plugin software for Minecraft Java, such as Paper, Purpur or Spigot, or a server that loads both plugins and mods, such as Arclight, Mohist or Magma. Mod loaders (Fabric, Quilt, Forge and NeoForge), Vanilla and proxies cannot load Geyser, so the switch does not appear on them. - The server must run Java 21 or newer, which is the Java for Minecraft 1.20.5 and newer. [Change Java settings](/docs/managed-containers/software/java-settings/) shows the Java your server runs. - If you added Geyser or Floodgate to the server yourself, remove them first, so the server does not load two copies. - On a server someone shared with you, you need the Install software permission. ## Turn on Bedrock crossplay 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the server, then the **Software** tab. 2. On the **Current software** card at the top, turn on **Bedrock crossplay**. 3. Wait while the switch is greyed out. We download both plugins into the server's `plugins` folder straight away. 4. Restart the server from the [console](/docs/managed-containers/console/), so it loads the plugins. Geyser also gets a port of its own, which the [Ports tab](/docs/managed-containers/ports/) lists as a **Managed** port. It does not count against your plan's ports. ## Find the Bedrock address Bedrock players add the server in the game with an address and a port. The **Bedrock players join at** line under the switch does not show the address yet, so find it one of these ways: - If players join the server by a hostname, such as a [free server's](/docs/managed-containers/free-servers/) join address, Bedrock players use the same hostname with port `19132`, the standard Bedrock port. - Otherwise, open the server's **Ports** tab and find the port marked **Managed** with the purpose `Bedrock (NetherNet)`. Bedrock players enter the IP address before the colon as the address, and the number after it as the port. - The API returns both in `bedrock_bridge.join`, as [With the API](#with-the-api) shows. The Java port does not work for Bedrock players. ## Change the Geyser settings Geyser keeps its settings in `plugins/Geyser-Spigot/config.yml`, and Floodgate keeps its settings in `plugins/floodgate/config.yml`. Edit them in the [Files tab](/docs/managed-containers/files/), then restart the server. Bedrock players reach Geyser through the port we gave it, so we set these Geyser settings again every time the server starts. A change to them does not last: | Setting | What we set | | --- | --- | | `bedrock.address` and `bedrock.port` | The address and number of Geyser's port | | `bedrock.clone-remote-port` | `false` | | `bedrock.transport` | `nethernet` | | `bedrock.webrtc-port` | `0`, so Geyser uses the same port for everything | | `bedrock.signaling.mode` | `builtin` | | `remote.address` | `auto` | | `remote.auth-type` | `floodgate` | | `advanced.bedrock.haproxy-protocol-whitelisted-ips` | The addresses of our proxies, which pass on each player's own address | Everything else in both files is yours to change. ## Keep the plugins up to date Each time the server starts, we check GeyserMC for new builds of Geyser and Floodgate and install them before the server boots. If GeyserMC does not answer within 20 seconds, the server starts with the builds it already has. You do not need to update the plugins yourself. The two plugins appear on the [Installed addons](/docs/managed-containers/software/installed-addons/) panel with the **Uploaded by hand** badge, because the Marketplace did not install them. Leave them there: to remove them, turn off the switch. ## Turn off Bedrock crossplay 1. On the server's **Software** tab, turn off **Bedrock crossplay** on the **Current software** card. 2. Restart the server, so it stops running Geyser. We delete `plugins/Geyser-Spigot.jar`, `plugins/floodgate-spigot.jar` and the folders `plugins/Geyser-Spigot` and `plugins/floodgate`, with any settings you changed in them. Geyser's port goes back to the pool. If you turn crossplay on again later, it starts afresh. Floodgate creates a new key, and Bedrock players keep their player data, because Floodgate identifies them by their Xbox account. When you [change the server software](/docs/managed-containers/software/) to software that cannot load Geyser, such as Fabric, Vanilla or a Bedrock server, we turn Bedrock crossplay off in the same way. ## Result The switch stays on, and a message says `Setting up Bedrock crossplay. Geyser and Floodgate install on the next start.` The plugins are in the `plugins` folder by then, and they load when the server restarts. Bedrock players can then join at the address you found, while Java players join as before. ## Troubleshooting The **Bedrock crossplay** switch is not on the card : The server cannot run Geyser. It runs a mod loader, Vanilla, a proxy or a Bedrock server, or it runs a Java older than 21. Change to a plugin server under [Server software](/docs/managed-containers/software/), or move to Minecraft 1.20.5 or newer on Java 21. The API gives the exact reason in `unsupported_reason`. **Could not change Bedrock crossplay** with `GeyserMC downloads are unavailable right now. Try again shortly.` : GeyserMC's download service did not answer, so the switch stays off. Turn it on again later. Bedrock players cannot connect : Restart the server after you turn the switch on, because the plugins load only when the server starts. Then check that players use the address from [Find the Bedrock address](#find-the-bedrock-address), not the Java port. Some Bedrock players can join and others cannot : Ask the players who cannot join to update Minecraft to the latest version. Older versions cannot join. You installed Geyser yourself and Bedrock players cannot connect : A copy of Geyser that you uploaded, or installed from a source other than GeyserMC, has no Bedrock port of its own. Remove it, then turn on the switch. A change to Geyser's config does not last : The setting is one we set at every start. See [Change the Geyser settings](#change-the-geyser-settings). ## Related - [Change the server software](/docs/managed-containers/software/) - [Manage server ports](/docs/managed-containers/ports/) - [Manage installed addons](/docs/managed-containers/software/installed-addons/) ## With the API Turn Bedrock crossplay on: ```bash curl -X POST https://api.coritan.com/api/v1/client/servers/$SERVER/software/bedrock \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"enabled": true}' ``` The request returns once both plugins are installed. The response has `enabled` (`true`), `state` (`ready`), `geyser_version`, `geyser_build`, `floodgate_version`, `floodgate_build` and `bedrock_port`, Geyser's port. Send `{"enabled": false}` to turn it off. The response is the new state, with `enabled` set to `false`, `state` set to `disabled` and the versions set to `null`. A body without `enabled` also turns crossplay off. `GET /api/v1/client/servers/{uuid}/software/context` shows the state under `bedrock_bridge`: `supported` : Whether the server can run Geyser. When it is `false`, `unsupported_reason` says why. `enabled` and `state` : `true` and `ready` while crossplay is on, `false` and `disabled` while it is off. `geyser_version`, `geyser_build`, `floodgate_version` and `floodgate_build` : The builds on the server. `last_checked_at` : When we last checked GeyserMC for new builds. `join` : `null` while crossplay is off. Otherwise, `address` and `port` are the hostname and port Bedrock players join at when the server has a hostname. `direct` is the server's IP address and Geyser's port, as `host:port`, and `null` on a free server. `bedrock_port` is Geyser's port. On a server with no hostname, give players `direct`. A server that cannot run Geyser answers `400` with the reason: - `This server runs mods rather than plugins. Geyser is a plugin, so it needs Paper, Spigot or another plugin-capable server.` - `Vanilla Minecraft cannot load plugins. Switch to Paper to add Bedrock support.` - `This server type does not support plugins.` - `Geyser needs Java 21 or newer and this server runs Java 17. Update the Minecraft version first.`, with the server's own Java version `GeyserMC downloads are unavailable right now. Try again shortly.` also answers `400`. On a server shared with you, turning crossplay on or off needs `software.install`. ## API - `POST /api/v1/client/servers/{uuid}/software/bedrock`: Turn Bedrock crossplay on or off for a Java server (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-post-api-v1-client-servers-uuid-software-bedrock) # Manage Bedrock packs > Import, turn on, turn off and delete behavior and resource packs on a Bedrock Dedicated Server. Source: https://www.coritan.com/docs/managed-containers/software/bedrock-packs/ In the dashboard: - /dashboard/servers/…/software: https://www.coritan.com/dashboard/servers A Bedrock add-on is made of one or more *packs*. A *behavior pack* changes how the world plays, and a *resource pack* changes how it looks and sounds. A Bedrock Dedicated Server keeps behavior packs in `behavior_packs` and resource packs in `resource_packs`, but the world loads only the packs on its own list. The **Packs** panel on a server's **Software** tab imports packs, turns them on and off in the world, and deletes them. Every change on the panel applies the next time the server starts. ## Before you begin - The panel is there for a Bedrock Dedicated Server only. To let Bedrock players join a Java server instead, see [Let Bedrock players join a Java server](/docs/managed-containers/software/bedrock-crossplay/). - Take a [snapshot](/docs/managed-containers/snapshots/) before you add packs to a world you care about. Some packs change the world in ways you cannot undo by turning them off. - On a server someone shared with you, the Install software permission lets you import packs, turn them on and off, and choose whether resource packs are required. Deleting a pack needs `software.delete`, which the dashboard's permission list does not offer, so the owner can grant it only [through the API](/docs/managed-containers/users/#with-the-api). To open the panel: 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the server, then the **Software** tab. 2. Select **Packs**. ## Read the Packs panel The line at the top names the world, such as `Bedrock level`. The world is the one `level-name` names in `server.properties`, and its lists of packs are in `worlds/Bedrock level/world_behavior_packs.json` and `world_resource_packs.json`. The **Behavior packs** and **Resource packs** cards list the packs in each folder and count how many are on. Packs that are on come first, then the ones that are off, each in name order. Each row shows the pack's name and version, its description and its folder. A badge tells you more: **Off** : The pack is in the folder, but the world does not load it. **Runs scripts** : The pack carries scripts. A pack like this may need the Beta APIs experiment turned on in the world before the world loads. The cards list only folders with a `manifest.json` at their top level. Select **Refresh** to read the folders again. ## Import a pack 1. Upload the pack file to the server root or to the `uploads` folder in the [Files tab](/docs/managed-containers/files/). It must end in `.mcaddon`, `.mcpack` or `.zip`. 2. On the **Packs** panel, find the file on the **Import a pack** card. 3. Select **Import pack** next to it. The import runs as a job, which appears in the jobs card at the top of the **Software** tab, and the file shows **Queued** until the job starts. The job unpacks the file and reads each pack's `manifest.json` to decide whether it is a behavior pack or a resource pack. It moves each pack into the right folder and turns it on in the world. When the job finishes, the panel lists the new packs. The import uses up the file you uploaded, even when it fails, so keep a copy. A few more rules apply: - A file can hold up to 25 packs. - A pack that is already on the server, with the same ID in its manifest, is replaced by the new build in its existing folder. - Skin packs, world templates and other packs a server does not load are skipped. - When the world has not been created yet, we create its lists, so the packs load when the server first creates the world. You can also copy a pack's folder into `behavior_packs` or `resource_packs` yourself. It appears on the panel with **Off**, and you turn it on as the next section describes. The [Marketplace](/docs/managed-containers/software/plugins-and-mods/) can search for add-ons too: choose `bedrock_addon` or `bedrock_resource_pack` in its kind list. A Marketplace install imports the file in the same way and turns its packs on. ## Turn a pack on or off 1. On the **Packs** panel, find the pack's row. 2. Turn the switch at the end of the row on or off. We add the pack to the world's list or remove it from the list. The pack's folder stays on the server. A message such as `"More Furniture" turned on. The world loads it on the next start.` confirms the change. Restart the server from the [console](/docs/managed-containers/console/) when you are ready. ## Recover a world a pack broke When a pack stops the server from starting, turn off every pack at once: 1. On the **Packs** panel, select **Turn off all packs…**. 2. Select **Turn off all packs**. We empty both of the world's lists, so the world starts on vanilla content the next time the server starts. The pack folders stay. Start the server, then turn the packs back on one at a time, restarting after each, to find the one that breaks the world. Delete that pack, or import a build that works with your server's version. ## Require resource packs Under the **Resource packs** card, the **Require resource packs** switch sets `texturepack-required` in `server.properties`: - On: players who decline the resource packs cannot join. - Off: players who decline join without the resource packs. Restart the server to apply the change. ## Delete a pack 1. On the **Packs** panel, select the bin icon at the end of the pack's row. 2. Select **Delete pack**. We remove the pack from the world's list and delete its folder from the server. You cannot undo this. To keep the pack's files, turn it off instead. ## Result Each change shows a message, such as `"More Furniture" deleted.` or `Resource packs set to required. It takes effect on the next start.` After the next start, the world loads the packs that are on. Updating the Bedrock server software keeps the pack folders and the world's lists. ## Troubleshooting The pack file is not on the **Import a pack** card : The card lists files in the server root and in `uploads` only, and only files ending in `.mcaddon`, `.mcpack` or `.zip`. Move the file there in the [Files tab](/docs/managed-containers/files/), then select **Refresh**. **Could not load the packs** : We could not read the server's files. Check that the server is installed, then select **Try again**. The import job fails with `No pack was found in that file: a pack needs a manifest.json at its top level` : The file holds no pack that we can find. A `.mcpack` must have `manifest.json` at its top level. A `.mcaddon` must hold `.mcpack` files at its top level, or pack folders at its top level or one folder down. Upload a file laid out that way. The import job fails with `No pack could be installed from that file: …` : Every pack in the file was skipped, and the message gives the reasons, such as `manifest.json is missing or invalid`, or a pack type that a server does not load. The import job fails with `That archive holds more than 25 packs` : Split the add-on into smaller files. The import job fails with `That file is no longer on the server` : Someone moved or deleted the file after it was listed. Upload it again and select **Refresh**. The server does not start after you added a pack : Follow [Recover a world a pack broke](#recover-a-world-a-pack-broke). Check the [console](/docs/managed-containers/console/) for the pack it names. A pack that is on has no effect : Restart the server: the world reads its lists only when it starts. If the pack shows **Runs scripts**, the world may need the Beta APIs experiment turned on. `Too many requests for this action. Please wait and try again.` : A server accepts 8 software actions, imports included, from one account in 10 minutes. The response says how many seconds to wait. ## Related - [Install plugins, mods and modpacks](/docs/managed-containers/software/plugins-and-mods/) - [Manage files](/docs/managed-containers/files/) - [Start a server in safe mode](/docs/managed-containers/software/safe-mode/) ## With the API The paths below are relative to `/api/v1/client/servers/{uuid}/software`. A pack's `{pack_uuid}` is the ID in its manifest, which the list returns as `uuid`. | Request | What it does | | --- | --- | | `GET /bedrock/packs` | Lists the packs, the world and the files you can import. | | `POST /bedrock/packs/import` | Queues an import of a file on the server. | | `POST /bedrock/packs/{pack_uuid}/active` | Turns a pack on with `{"active": true}` or off with `{"active": false}`. | | `POST /bedrock/packs/disable-all` | Turns off every pack in the world. | | `POST /bedrock/packs/{pack_uuid}/remove` | Deletes a pack. | | `PUT /bedrock/texturepack-required` | Requires resource packs with `{"required": true}`, or not with `{"required": false}`. | Import a file you uploaded: ```bash curl -X POST https://api.coritan.com/api/v1/client/servers/$SERVER/software/bedrock/packs/import \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"path": "/uploads/more-furniture.mcaddon"}' ``` `path` is required. The optional `kind` is `bedrock_addon`, the default, or `bedrock_resource_pack`; the manifests decide where each pack goes either way. The response is the install job, as [Install plugins, mods and modpacks](/docs/managed-containers/software/plugins-and-mods/#install) describes. When the job finishes, its `status` is `succeeded`, or `succeeded_with_warnings` when a pack was skipped, runs scripts or went into a world that did not exist yet. Its `detail` lists the imported `packs` and any `warnings`. `GET /bedrock/packs` returns: `level_name` and `world_dir` : The world, and its folder, such as `/worlds/Bedrock level`. `behavior` and `resources` : The packs in each folder. Each has `uuid`, `name`, `description`, `version_string`, `pack_type`, `has_scripts`, `folder`, `path` and `active`. `tracked` is `true` when an import or the Marketplace installed the pack, with its `record_uuid`. `active_count` : How many packs are on. `texturepack_required` : Whether resource packs are required. `archives` : The files you can import, each with `filename`, `path`, `size_bytes` and `modified_at`. Turning a pack on or off returns the pack with `active` and `restart_required`. `disable-all` returns how many packs it turned off in `disabled`, as `behavior` and `resources` counts. `remove` returns `{"deleted": true}` with the pack's `uuid`, `name`, `path` and `pack_type`. `texturepack-required` returns `texturepack_required` and `restart_required`. A refused request answers `400` with the reason, such as `path is required`, `active is required`, `required is required`, `Only .mcaddon, .mcpack and .zip files can be imported as packs`, `That pack is not installed on this server` or `Bedrock packs only apply to a Bedrock Dedicated Server`. Deleting while a job that replaces the server's files is running answers `409`. Only imports count towards the limit of 8 software actions in 10 minutes. On a server shared with you, listing needs `software.read`. Importing, turning packs on or off, `disable-all` and `texturepack-required` need `software.install`, and deleting needs `software.delete`. ## API - `GET /api/v1/client/servers/{uuid}/software/bedrock/packs`: Behavior and resource packs on disk, which are active in the world, and importable uploads (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-get-api-v1-client-servers-uuid-software-bedrock-packs) - `POST /api/v1/client/servers/{uuid}/software/bedrock/packs/disable-all`: Empty the world's activation lists so it boots on vanilla content; folders stay (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-post-api-v1-client-servers-uuid-software-bedrock-packs-disable-all) - `POST /api/v1/client/servers/{uuid}/software/bedrock/packs/import`: Queue an import of an uploaded .mcaddon / .mcpack / .zip already on the server (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-post-api-v1-client-servers-uuid-software-bedrock-packs-import) - `POST /api/v1/client/servers/{uuid}/software/bedrock/packs/{pack_uuid}/active`: Add a pack to, or drop it from, the world's activation list (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-post-api-v1-client-servers-uuid-software-bedrock-packs-pack-uuid-active) - `POST /api/v1/client/servers/{uuid}/software/bedrock/packs/{pack_uuid}/remove`: Deactivate a pack, delete its folder, and drop its tracking row (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-post-api-v1-client-servers-uuid-software-bedrock-packs-pack-uuid-remove) - `PUT /api/v1/client/servers/{uuid}/software/bedrock/texturepack-required`: Texturepack-required: players must accept the resource packs to join (https://www.coritan.com/docs/api/reference/client/container-apps/servers-software/#op-put-api-v1-client-servers-uuid-software-bedrock-texturepack-required) # Import a server from another host > Copy a server's files from another host over SFTP or FTP into your Coritan server with the Import tab. Source: https://www.coritan.com/docs/managed-containers/import/ In the dashboard: - /dashboard/servers/…/import: https://www.coritan.com/dashboard/servers The **Import** tab copies a server's files from another host into your Coritan server over SFTP or FTP. Use it to bring a world, its plugins and its settings over from another hosting company. It copies files only, so the server's software stays as it is. ## Before you begin - Get the file access details from the other host's panel: the address, the port, the username and the password for SFTP or FTP. FTPS is not supported: use SFTP, or plain FTP if the other host offers it. - Check that the files fit. Everything in the folder you import must fit in this server's disk space. - The folder you import can hold at most 250,000 files and folders. For a larger tree, import one sub-folder at a time. - The other host must be on a public address. We refuse private and local addresses. - The server cannot be installing, moving to another machine or suspended. - On a server someone shared with you, the owner must have given you the Files permission. ## Import the files 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the server, then the **Import** tab. 2. Under **Protocol**, choose **SFTP** or **FTP**. 3. In **Host**, enter the other host's address, for example `sftp.example.com`. 4. In **Port**, enter the port the other host gave you. Leave it empty for the usual port: 22 for SFTP and 21 for FTP. 5. Enter the **Username** and **Password** from the other host. 6. In **Remote folder**, enter the folder whose contents should land in this server's root folder. `/` copies everything the account can see. If the other host keeps the server in `/minecraft`, enter `/minecraft`, and `/minecraft/world` becomes `/world` here. 7. To delete every file on this server before copying, tick **Wipe this server first**. Leave it clear to keep the files that are already here. 8. Select **Test connection**. We sign in and count what we would copy, without writing anything. **Connected** shows how many files and folders the remote folder holds. 9. Select **Start import**. If you ticked **Wipe this server first**, the button reads **Wipe and import…**: type the server's name to confirm, then select **Wipe and import**. The **Import in progress** card shows the status, a meter with the files and bytes copied so far, and the file being copied. You can leave the page: the import carries on, and the tab shows it again when you come back. ## What the import does - If the server is running, we stop it before copying and leave it stopped. - With **Wipe this server first**, we delete every file on the server first. - We copy every file and folder under the remote folder, empty folders included. A file with the same path as one on the server replaces it, and files that exist only on this server stay. - We skip any single file that is larger than the server's whole disk space, and list it as a file we could not copy. - On a free server, we keep the server program we installed and skip any file from the other host that would replace it. - If the connection drops, we reconnect and carry on where the copy stopped. The import fails if it runs for more than 6 hours, or once 200 files could not be copied. ## Stop an import 1. On the **Import in progress** card, select **Stop import…**. 2. Select **Stop import** to confirm. The status reads `Stopping` until the copy halts, then `Cancelled`. Files the import already copied stay on the server. A server runs one import at a time, so stop the running one before you start another. ## Result The status moves from `Queued` to `Copying`, then to `Completed`, `Failed` or `Cancelled`. When it completes, the card says `Import finished. Check the Files tab, then start the server.` If any files could not be copied, the card lists each one with its reason. Check the files on the [Files tab](/docs/managed-containers/files/), then start the server from the [console](/docs/managed-containers/console/). If the other host ran different software or a different version, [change the server software](/docs/managed-containers/software/) to match before you start it. Select **Dismiss** to hide the card of a finished import. ## Troubleshooting **Could not connect** : The test failed, and the message under it gives the reason. Check the host, port, username and password against the other host's panel. Many game hosts use a port other than 22 for SFTP. `Remote listing timed out` : The test lists the remote folder for up to 60 seconds, and a large folder can take longer. Start the import anyway: the import itself allows 10 minutes to list the folder. `FTPS is not supported; use SFTP, or plain FTP if the other host offers it` : The address starts with `ftps://`. Use the other host's SFTP details instead. `Could not reach sftp.example.com:22: connection timed out` : We could not open a connection. Check the address and port, and that the other host allows connections from outside. `Unable to resolve hostname: sftp.example.com` : The address does not exist. Check its spelling. `resolved address targets a private or reserved network` : The address points at a private network, which we cannot reach. Use the other host's public address. `The remote folder holds more than 250,000 files and folders; the importer copies at most that many. Import a sub-folder instead.` : Import the folder in parts, one sub-folder at a time, with **Remote folder** set to each. `The remote folder holds … but this server has … of disk` : The files do not fit. Import less, or move to a plan with more disk. `Cannot import while the server is installing` : Wait until the installation finishes, then start the import. The same message names a server that is suspended or moving. `An import is already running on this server` : The tab shows the running import. Wait for it to finish, or stop it first. `Stopped after 200 files could not be copied` : The other host refused to send many files. Read the reasons in the list, fix the file permissions on the other host, and import again. `Import timed out` : The import ran for more than 6 hours. Import the rest in parts, with **Remote folder** set to each sub-folder. `Too many requests for this action. Please wait and try again.` : You tested or started more than 12 imports on this server in 5 minutes. Wait a few minutes and try again. ## Related - [Manage server files](/docs/managed-containers/files/) - [Connect to a server with SFTP](/docs/managed-containers/sftp/) to upload files yourself instead. - [Take a server snapshot](/docs/managed-containers/snapshots/) before you import over files you want to keep. ## With the API The routes take the same body. `type` is `sftp` or `ftp`, `remote_host` is the address, `remote_port` defaults to 22 or 21, `username` and `password` are the other host's login, `base_path` is the remote folder (default `/`), and `truncate` set to `true` wipes the server first. Test the connection with `POST /api/v1/client/servers/{uuid}/import/test-connection`: ```bash curl -X POST https://api.coritan.com/api/v1/client/servers/$SERVER/import/test-connection \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"type": "sftp", "remote_host": "sftp.example.com", "remote_port": 2022, "username": "alex", "password": "old-host-password", "base_path": "/minecraft"}' ``` It answers `{"success": true, "files": 1843, "directories": 212}`, or `success` set to `false` with a `message`. Send the same body to `POST /api/v1/client/servers/{uuid}/import/start` to start the import. It signs in to the other host first and answers `400` if that fails, so a wrong password never becomes a queued import. The start route and the status routes return the import: ```json { "import_id": "0b7f9c1e-5d2a-4f61-9a43-2c8e7d10b6a5", "status": "in_progress", "files_transferred": 412, "total_files": 1843, "bytes_transferred": 734003200, "total_bytes": 2147483648, "current_file": "/minecraft/world/region/r.0.0.mca", "errors": [], "cancel_requested": false, "truncate": false, "stopped_server": true } ``` `status` is `pending`, `in_progress`, `cancelling`, `completed`, `failed` or `cancelled`. `errors` lists the files that could not be copied, each as `file` and `error`. `stopped_server` is `true` when we stopped the server for the import. | Route | What it does | |---|---| | `GET /api/v1/client/servers/{uuid}/import/status/{import_id}` | Returns one import. | | `GET /api/v1/client/servers/{uuid}/import/current` | Returns `{"import": …}` with the running import, or the last one if it finished in the past 24 hours, or `null`. | | `DELETE /api/v1/client/servers/{uuid}/import/{import_id}` | Stops the import. One that has not begun is `cancelled` at once; one that is copying is `cancelling` until it halts. | Starting an import while one runs answers `409` with `message`, `import_id` and `status` of the running one. Testing and starting share a limit of 12 requests per server in 5 minutes. Testing and reading need `file.read`, starting and stopping need `file.create`, and a start with `truncate` also needs `file.delete`. ## API - `GET /api/v1/client/servers/{uuid}/import/current`: Active import, or a recently finished one, so a refresh can resume the UI (https://www.coritan.com/docs/api/reference/client/container-apps/servers-import/#op-get-api-v1-client-servers-uuid-import-current) - `POST /api/v1/client/servers/{uuid}/import/start`: Queue a remote tree copy onto this server (https://www.coritan.com/docs/api/reference/client/container-apps/servers-import/#op-post-api-v1-client-servers-uuid-import-start) - `GET /api/v1/client/servers/{uuid}/import/status/{import_id}`: Poll an import the caller started (https://www.coritan.com/docs/api/reference/client/container-apps/servers-import/#op-get-api-v1-client-servers-uuid-import-status-import-id) - `POST /api/v1/client/servers/{uuid}/import/test-connection`: Probe a remote SFTP/FTP host and count files without writing anything (https://www.coritan.com/docs/api/reference/client/container-apps/servers-import/#op-post-api-v1-client-servers-uuid-import-test-connection) - `DELETE /api/v1/client/servers/{uuid}/import/{import_id}`: Stop a pending or in-progress import (https://www.coritan.com/docs/api/reference/client/container-apps/servers-import/#op-delete-api-v1-client-servers-uuid-import-import-id) # Create server rules > Create automation rules on the Rules tab, which we store but do not run yet. Source: https://www.coritan.com/docs/managed-containers/rules/ In the dashboard: - /dashboard/servers/…/rules: https://www.coritan.com/dashboard/servers The **Rules** tab holds a server's automation rules. A rule names a *trigger*, optional *conditions* and the *actions* to take, each written as JSON. > [!WARNING] > We do not run rules yet. A rule you create is saved, but nothing triggers it, so it never restarts the server or sends a command. To run a command, a power action or a snapshot at set times, use a [schedule](/docs/managed-containers/schedules/). ## Before you begin - On a server someone shared with you, the owner must have given you the Manage rules permission. - The rule's fields take JSON. Put names and text in double quotes, for example `{"type": "power", "action": "restart"}`. ## Create a rule 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the server, then the **Rules** tab. 2. Select **New rule…**. 3. In **Name**, enter a name, for example `Restart on crash`. 4. Under **Trigger**, choose what the rule reacts to: **Manual**, **Scheduled**, **Event**, **Crash** or **Resource threshold**. 5. In **Trigger config**, enter the trigger's settings as JSON, or leave `{}`. 6. In **Conditions**, enter what must be true for the rule to act, or leave `{}` for none. 7. In **Actions**, enter what the rule does. 8. Leave **Enabled** ticked to switch the rule on, or clear it to save it switched off. 9. Select **Create rule**. ## Rule format We store each field as you enter it and check only that it is JSON. **Actions** takes one action, or a list of them: | Action | Example | |---|---| | Run a console command | `{"type": "command", "command": "say Restarting in 5 minutes"}` | | Send a power signal | `{"type": "power", "action": "restart"}` with `start`, `stop`, `restart` or `kill` | **Conditions** can limit a rule to a window of hours in UTC, for example `{"type": "time_window", "start_hour": 2, "end_hour": 5}` for 02:00 to 05:59. ## Turn a rule off or delete it - To turn a rule off or on, use the switch in its **Enabled** column. - To delete a rule, select the bin icon at the end of its row, then **Delete rule**. ## Result The dashboard confirms a new rule with `Rule created.`. The list shows each rule's name, its **Trigger**, when you created it and whether it is enabled. Under the name, a rule that has never acted reads `Never fired`, which is what every rule shows while we do not run them. ## Troubleshooting `the Actions field is not valid JSON.` : The field it names holds something other than JSON. Check the brackets and put every name and text value in double quotes. `Could not update the rule` or `Could not delete the rule` : Turning a rule off and deleting a rule fail with a server error at the moment. A rule has no effect while we do not run rules, so leaving it in place does no harm. [Contact support](https://www.coritan.com/dashboard/support) if you need it removed. `Insufficient permissions` : On a server someone shared with you, ask the owner for the Manage rules permission. ## Related - [Schedule server tasks](/docs/managed-containers/schedules/) runs commands, power actions and snapshots at set times. - [Share a server with other users](/docs/managed-containers/users/) explains the Manage rules permission. ## With the API Create a rule with `POST /api/v1/client/servers/{uuid}/rules`. The body takes `name`, `trigger_type` (the dashboard offers `manual`, `scheduled`, `event`, `crash` and `resource`), `trigger_config` (an object, default `{}`), `conditions` (an object, a list or `null`), `actions` (an object or a list) and `enabled` (default `true`): ```bash curl -X POST https://api.coritan.com/api/v1/client/servers/$SERVER/rules \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "Restart on crash", "trigger_type": "crash", "actions": [{"type": "power", "action": "restart"}]}' ``` The response (`201`) is the rule, with `uuid`, `name`, `trigger_type`, `trigger_config`, `conditions`, `actions`, `enabled`, `created_at`, `updated_at` and `last_triggered_at`, which stays `null` while we do not run rules. | Route | What it does | |---|---| | `GET /api/v1/client/servers/{uuid}/rules` | Lists the server's rules, oldest first. | | `PUT /api/v1/client/servers/{uuid}/rules/{rule_uuid}` | Changes the fields you send. It answers `500` at the moment. | | `DELETE /api/v1/client/servers/{uuid}/rules/{rule_uuid}` | Deletes the rule. It answers `500` at the moment. | Creating, changing and deleting need `settings.automation`. Any user on the server can list its rules. ## API - `GET /api/v1/client/servers/{uuid}/rules`: List rules for a server (https://www.coritan.com/docs/api/reference/client/container-apps/servers-rules/#op-get-api-v1-client-servers-uuid-rules) - `POST /api/v1/client/servers/{uuid}/rules`: Create a new rule (https://www.coritan.com/docs/api/reference/client/container-apps/servers-rules/#op-post-api-v1-client-servers-uuid-rules) - `PUT /api/v1/client/servers/{uuid}/rules/{rule_uuid}`: Update a rule (https://www.coritan.com/docs/api/reference/client/container-apps/servers-rules/#op-put-api-v1-client-servers-uuid-rules-rule-uuid) - `DELETE /api/v1/client/servers/{uuid}/rules/{rule_uuid}`: Delete a rule (https://www.coritan.com/docs/api/reference/client/container-apps/servers-rules/#op-delete-api-v1-client-servers-uuid-rules-rule-uuid) # Install server updates > See which plugins, mods and packs have a newer build for your server, and install the updates from the Software tab. Source: https://www.coritan.com/docs/managed-containers/updates/ In the dashboard: - /dashboard/servers/…/updates: https://www.coritan.com/dashboard/servers The **Updates** tab lists the plugins, mods, datapacks and Bedrock packs on your server that have a newer build. It covers what you installed from the [marketplace](/docs/managed-containers/software/plugins-and-mods/), since we know where each of those came from. Files you uploaded yourself are not checked. ## Before you begin - The server software and a modpack are not listed here. Move those to a new version under **Server software** on the **Software** tab, as [Change the server software](/docs/managed-containers/software/) describes. - On a server someone shared with you, installing an update needs the Install software permission. ## See which updates are available 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the server, then the **Updates** tab. 2. Read the list. Each row shows the **Package**, the **Current** build on the server, the build that is **Available** and the **Source** it comes from. 3. To check again, select the refresh button next to the title. We only offer a build that fits the server's software and game version. We prefer release builds over beta and alpha builds, and offer a build only when it is newer than the one installed. ## Install an update The **Install** and **Install all** buttons on the **Updates** tab do not work at the moment: they fail with `Could not queue the update`. Install updates from the **Software** tab instead: 1. Open the **Software** tab, then **Installed addons**. 2. Find the addon. A row with an update shows a badge that names the new build, for example `Update to 2.11.0`. 3. Select **Update** on its row. We remove the old file and install the new build as a job, which the **Software** tab follows. The new build loads the next time the server starts. [Manage installed addons](/docs/managed-containers/software/installed-addons/) has the details. ## Result When every tracked addon is on its newest compatible build, the tab shows **Everything is up to date**. ## Troubleshooting `Could not queue the update` : The buttons on the **Updates** tab fail at the moment. Update the addon from **Installed addons** on the **Software** tab. **Could not check for updates** : We could not reach a source to compare builds. Select **Try again**, or check again later. An addon you installed is missing from the list : The list shows only addons with a newer build that fits your server. A file you uploaded yourself, or copied in with an import, is never checked. ## Related - [Manage installed addons](/docs/managed-containers/software/installed-addons/) - [Install plugins, mods and modpacks](/docs/managed-containers/software/plugins-and-mods/) - [Change the server software](/docs/managed-containers/software/) ## With the API `GET /api/v1/client/servers/{uuid}/updates` returns `{"updates": […]}` with one entry per addon that has a newer build: ```json { "updates": [ { "record_uuid": "3f2c8a61-0d4e-4b7a-9e15-6a2b7c9d0e41", "display_name": "LuckPerms", "kind": "plugin", "source_name": "Modrinth", "install_path": "/plugins/LuckPerms-Bukkit-5.4.141.jar", "current_version": "vKq6e7Sf", "current_version_name": "5.4.141", "latest_version": "Rk3eYx1m", "latest": {"version_id": "Rk3eYx1m", "name": "5.4.145", "release_date": "2026-09-12T08:30:00Z", "version_type": "release"}, "update_available": true, "checked_at": "2026-09-25T14:30:00+00:00" } ] } ``` `current_version` and `latest_version` are the source's own build ids, and `current_version_name` and `latest.name` are the version names people read. To install an update, send `POST /api/v1/client/servers/{uuid}/software/{record_uuid}/update` with the entry's `record_uuid`, as [Manage installed addons](/docs/managed-containers/software/installed-addons/#with-the-api) shows. `GET /api/v1/client/servers/{uuid}/software/updates` gives the same check with more options. Any user on the server can read the list. ## API - `GET /api/v1/client/servers/{uuid}/updates`: Get available updates for server software (https://www.coritan.com/docs/api/reference/client/container-apps/servers/#op-get-api-v1-client-servers-uuid-updates) # Server settings > What the Settings tab shows about a server, from its address and resources to its startup command and tags. Source: https://www.coritan.com/docs/managed-containers/settings/ In the dashboard: - /dashboard/servers/…/settings: https://www.coritan.com/dashboard/servers The **Settings** tab of a server shows the facts you look up most: its address, what the plan gives it, and the command it starts with. Only the tags can be edited here. Open it from the server page in the [dashboard](https://www.coritan.com/dashboard/servers) with the **Settings** tab. The name, the limits and the address are set when we set the server up. To change the resources, change the plan from the server's service page, which **Plan and billing** opens; see [Billing](/docs/billing/). The dashboard has no way to rename a server. ## Tags Your own labels for the server. They appear on the **Container Apps** page and in the services list, and you can filter both by them. Type a tag in **Add a tag** and press Enter; a tag uses lowercase letters, digits, dots, dashes and slashes. Select the cross on a tag to remove it. Each change saves at once. [Tags](/docs/get-started/tags/) explains how tags work across your account. ## General **Name** : The name you gave the server when you ordered it. **UUID** : The server's ID. The API and support use it to identify the server. **Copy UUID** copies it. **Software** : The software the server runs, with its catalogue slug in brackets when that differs from the name. **Image** : The container image the software runs in. **Node** : The machine the server runs on, and its location. **Created** : When the server was set up. **Status** : The server's lifecycle status, such as `running`, `stopped`, `installing` or `suspended`. **Plan and billing** opens the server's service page, where you change the plan or cancel the server. ## Network The primary address players and apps connect to. **Open Ports** opens the [Ports tab](/docs/managed-containers/ports/), where you add more ports or change the primary one. **IP address** : The IP address alone. **Copy IP address** copies it. **Address with port** : The IP address and port together, such as `203.0.113.10:10565`. **Copy address** copies it. ## Resources What the plan gives this server. | Field | What it means | | --- | --- | | **Memory** | Memory in MB, with the same figure in GB below it. | | **Disk** | Disk space in MB, with GB below it. | | **CPU** | CPU limit as a percentage, where `100%` is one core. The figure below it gives the number of cores. `0%` means no limit. | | **Database slots** | How many databases you can create on the **Databases** tab. | | **Backup slots** | How many backups the server can keep. | | **Port slots** | How many ports the server can hold, including the primary one. | ## Startup variables The software template sets these values, such as the game version or the file the server starts. The **Software** tab changes them when it switches versions, so treat this table as read-only. Each value has a copy button. ## Startup command The command the server runs when it starts, with the startup variables filled in. The copy button copies it, for example to run the same server elsewhere. ## With the API `GET /api/v1/client/servers/{uuid}` returns everything this tab shows. ```bash curl https://api.coritan.com/api/v1/client/servers/$SERVER \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` | Tab field | Response field | | --- | --- | | **Name** | `name` | | **UUID** | `uuid` | | **Software** | `template_name`, `template_slug` | | **Image** | `docker_image` | | **Node** | `node_name`, `location_name` | | **Created** | `created_at` | | **Status** | `status` | | **IP address** and **Address with port** | `ip_address`, `port` | | **Memory**, **Disk**, **CPU** | `memory_mb`, `disk_mb`, `cpu_percent` | | **Database slots**, **Backup slots**, **Port slots** | `database_limit`, `backup_limit`, `allocation_limit` | | **Startup variables** | `variables` | | **Startup command** | `startup_command` | | **Tags** | `tags` | | **Plan and billing** | `service_id`, the ID of the server's service | ## API - `GET /api/v1/client/servers/{uuid}`: Get server details (https://www.coritan.com/docs/api/reference/client/container-apps/servers/#op-get-api-v1-client-servers-uuid) # How free servers work > What a free server includes, how it sleeps when empty and wakes when someone joins, and what it leaves out. Source: https://www.coritan.com/docs/managed-containers/free-servers/ A *free server* is a server on the free plan. It runs only while someone uses it: it goes to sleep when nobody is playing and starts again when a player joins or when you wake it. Coritan does not sell the free plan on coritan.com, and an order for it answers `This plan is not sold here.` This page explains how a free server you already have behaves. ## Sleeping and waking A free server stops after a set number of minutes without players. The default is 10 minutes, and it can be shorter while the machine is busy; the line under **This server is asleep** on the **Console** tab gives the current figure. A server you have just woken stays up for at least 10 minutes, so you have time to join. Stopping frees the memory for other servers, and your files stay where they are. To start it again, open the server's **Console** tab and select **Wake server**, or join it from the game. **Start** does not work on a free server that is down: it answers `Free servers must be started via the wake endpoint, not a direct power start/restart`. **Restart** works while the server runs. A wake goes through a start queue, so the machine is never asked to start more servers than it has room for: - **Waiting for a free slot** means the start is queued. The banner shows your position, such as `Position 2 of 5 in the start queue.`, and the page updates by itself. - **Starting your server** means your turn has come and the server is booting. - When the machine is full, the banner says so, and the start waits in the queue until there is room. If your account has hit a start limit, the banner asks you to complete a verification check before **Wake server** works. ## Joining a free server A free server has no IP address or port of its own. Players join at its *join address*, a hostname that works for Java and Bedrock with no port to type. The dashboard does not show this address yet; the API returns it (see [With the API](#with-the-api)). ## What the free plan leaves out | Feature | On a free server | | --- | --- | | Backups | Not included. Download your world from the file manager instead. | | Databases | Not included. | | Schedules | Not included. | | Snapshots | Included, with room for one snapshot. | | Snapshot download | Not included. Restore the snapshot onto any of your servers, or download files from the file manager. | | Unused chunks | Always removed while the world sleeps: we delete chunks no player has ever visited, and the ground regenerates the same way if anyone goes there. A paid plan lets you turn this off. | An action the plan leaves out answers `403` with `upgrade_required` set to `true` and a `message` that names the feature, such as `Databases are not included on the free plan. Upgrade to a paid plan to run plugins that need one.` The **Port slots** figure on the [Settings tab](/docs/managed-containers/settings/) shows how many ports the plan allows. ## Free servers nobody uses By default, we reclaim a free server that nobody joins or starts for three weeks: - After 14 days without activity, we take a snapshot of the server and suspend it. A suspended server cannot be started from the dashboard. - After 21 days without activity, we delete it. Joining the server or starting it resets the count. To bring back a suspended free server before it is deleted, [contact support](https://www.coritan.com/dashboard/support). After deletion, the snapshot is your way back: [restore it onto another server](/docs/snapshots/restore-a-snapshot/). ## With the API `GET /api/v1/client/servers/{uuid}/sleep` returns the sleep state in `sleep` and the plan's features in `entitlements`. For a paid server, `sleep` is `{"policy": "none", "phase": "awake"}`. ```bash curl https://api.coritan.com/api/v1/client/servers/$SERVER/sleep \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` In `sleep`, the fields you need are: `phase` : `awake`, `asleep`, `queued` or `starting`, or the server's status (`installing`, `transferring`, `install_failed` or `suspended`) when that stops it from running. `idle_timeout_seconds` : How long the server waits without players before it sleeps. `sleeps_at` : When the server will sleep if nobody joins, once that is certain; otherwise `null`. `queue` : While a wake is queued or starting, the `position`, the `total` in the queue and the `status`; otherwise `null`. `capacity_available` : Whether a start would be admitted right now. When it is `false`, `capacity_reason` says why. `challenge_required` : Whether a wake needs a verification token. Each item in `entitlements` has a `feature`, `allowed`, `limit`, `reason`, `summary` and `detail`. For a free server, the item whose `feature` is `direct_endpoint` names the join address in its `summary` and `detail`. `POST /api/v1/client/servers/{uuid}/wake` asks for a sleeping free server to start. Send an empty body, or `{"turnstile_token": "..."}` when `challenge_required` is `true`. The response is the new `sleep` state. ```bash curl -X POST https://api.coritan.com/api/v1/client/servers/$SERVER/wake \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` A paid server answers `409` with `This server does not sleep; use the power action to start it.` A server that is installing, moving, suspended or whose install failed answers `409` with its status. Wakes are limited to 48 per server in 60 seconds. ## API - `GET /api/v1/client/servers/{uuid}/sleep`: Sleep and start-queue state for a free server (https://www.coritan.com/docs/api/reference/client/container-apps/servers/#op-get-api-v1-client-servers-uuid-sleep) - `POST /api/v1/client/servers/{uuid}/wake`: Ask for a sleeping free server to be started (https://www.coritan.com/docs/api/reference/client/container-apps/servers/#op-post-api-v1-client-servers-uuid-wake) # Server limits and rate limits > The sizes, counts and request rates that apply to servers, their files, backups, databases and actions. Source: https://www.coritan.com/docs/managed-containers/limits/ This page lists every fixed limit on a server, with what happens when you reach it. The plan sets the rest, and the page that covers each feature explains how to work within it. ## Plan The plan you order sets these. The **Resources** section of the server's **Settings** tab shows them, and [changing the plan](/docs/billing/change-plan/) changes them. | What | Limit | | --- | --- | | **Memory**, **Disk** and **CPU** | Set by the plan. | | Ports | **Port slots**, including the primary port. Most plans allow 5. Over it: `Allocation limit reached (5). Cannot add more ports to this server.` | | Databases | **Database slots**. Over it: `Database limit reached (3/3)`, with your count and the plan's limit. | | Snapshot storage | Set by the plan, and pooled across the account. [How snapshot storage works](/docs/snapshots/storage-allowance/) explains it. | | Server name | 2–60 characters, chosen when you order. The dashboard has no way to rename a server. | ## Console | What | Limit | | --- | --- | | Consoles open at once | 8 on one server and 32 across all servers, for each account. One more does not connect: its socket closes with code `4029`, and the page shows `Connection limit reached. Close other console sessions first.` | | Command length | 1,000 characters. The console cuts a longer command at that length, and `POST /command` answers `422`. | | Commands per second | 20 on one console. The console drops each command over that and shows `Slow down`. | | Output kept on the page | The last 2,000 lines. | ## Rate limits We count these actions per account and per server. Each limit covers a rolling window. | Action | Most per window | Window | | --- | --- | --- | | Power actions: start, stop, restart and kill | 24 | 60 seconds | | Wakes of a free server | 48 | 60 seconds | | Software actions: changes, installs, addon updates, resource pack and Java settings changes, and Bedrock pack imports | 8 | 10 minutes | | Compressing and extracting archives | 24 | 60 seconds | | Downloads from a URL | 24 | 60 seconds | | Import tests and starts | 12 | 5 minutes | | New databases | 24 | 60 seconds | | Backup restores | 8 | 5 minutes | A request over the limit fails, and the dashboard shows `Too many requests for this action. Please wait and try again.` Through the API, the request answers `429`: ```json { "detail": { "error": "rate_limited", "message": "Too many requests for this action. Please wait and try again.", "action": "container.power", "retry_after_seconds": 60 } } ``` `action` is `container.power`, `container.wake`, `container.software`, `container.file_compress`, `container.file_pull`, `container.import`, `container.database` or `container.backup_restore`. `retry_after_seconds` and the `Retry-After` header give the length of the window. A request refused this way does not count towards the limit. Taking and restoring snapshots have no rate limit of their own. A server runs one snapshot or restore at a time instead. Every API request also counts towards the limits for your account as a whole, which [Rate limits](/docs/api/rate-limits/) describes. ## Files | What | Limit | | --- | --- | | Path | 1,024 characters. Over it: `Path is too long`. | | File or folder name | 255 characters, with no `/`. Over it: `That file name is too long for the filesystem.` | | Items in one action | 200, such as files to delete or move at once. Over it: `Cannot operate on more than 200 items at once`. | | Upload in the dashboard | 100 MB per file. Over it: `File exceeds maximum upload size of 100 MB`. Upload larger files over [SFTP](/docs/managed-containers/sftp/). | | Saving a text file | 8 MB. Over it: `File is larger than 8 MB`. | | Opening a file in the editor | 2 MB. A larger file downloads instead. | | Download from a URL | A public `http` or `https` address, up to 2,048 characters. | | Extracting an archive on a free server | 1 GB. | | Changing files | Not while the server is installing or being moved: `File changes are paused while the server is installing.` | [Manage server files](/docs/managed-containers/files/) covers each action. ## Import | What | Limit | | --- | --- | | Files and folders | 250,000 in the folder you import. | | One file | No larger than the server's disk. A larger file is skipped. | | Listing the remote folder | 60 seconds for a test, 10 minutes for an import. | | Length of an import | 6 hours. After that: `Import timed out`. | | Failed files | 200. After that: `Stopped after 200 files could not be copied`. | | Imports at once | One per server. | [Import a server from another host](/docs/managed-containers/import/) explains the import. ## Snapshots | What | Limit | | --- | --- | | Snapshot name | 191 characters. | | **Leave out** patterns | 50, each up to 255 characters and 4,000 characters in all. | | Snapshots or restores at once | One per server. | | Time to finish | 12 hours. A snapshot still unfinished then is marked failed. | | Download link | Works for 6 hours by default. | | Free servers | Enough storage for one snapshot, and no download. | | Storage | The account's snapshot allowance. [How snapshot storage works](/docs/snapshots/storage-allowance/) explains what happens when it is full. | ## Backups | What | Limit | | --- | --- | | New backups | None. Snapshots have replaced backups, and creating one answers `410`. | | Download link | Works for 10 minutes. | | Restores | 8 per server in 5 minutes. | ## Databases | What | Limit | | --- | --- | | Databases per server | The plan's **Database slots**. Free servers have none. | | Name suffix | Lowercase letters, digits and `_`, up to 48 characters. | | Password | 24 random characters, set by us. | ## Schedules | What | Limit | | --- | --- | | Schedules per server | 25. Over it: `This server already has the maximum of 25 schedules. Delete one before creating another.` | | Tasks per schedule | 25. Over it: `A schedule can hold at most 25 tasks.` | | How often a schedule runs | At most once every 60 seconds. | | Name | 120 characters. | | Command in a task | 1,000 characters. Over it: `Command must be at most 1000 characters.` | | Delay before a task | 24 hours (86,400 seconds). Over it: `Task delay cannot exceed 24 hours.` | | Run history | The last 20 runs in the dashboard, and up to 100 through the API. | | Free servers | No schedules. | ## Ports | What | Limit | | --- | --- | | Ports on the shared address | The plan's **Port slots**, from the range 10000–40000. Port `25565` is reserved there. | | Ports on a floating IP | 16, from 1024 to 65535. They do not count against the plan's limit. | | Note on a port | 64 characters. | | Managed ports | Do not count against the plan's limit. | ## Users | What | Limit | | --- | --- | | Users on one server | No fixed number. Each one needs a Coritan account. | ## Software | What | Limit | | --- | --- | | Dependencies installed with an addon | 25, through the API. | | Packs in one Bedrock file | 25. | | Resource pack link | 2,048 characters. | | Resource pack prompt | 1,000 characters. | | Resource pack SHA-1 | 40 hexadecimal characters. | | Java memory | From half the plan's memory (at least 1024 MB) to the whole plan, in steps of 256 MB. The default is 80% of the plan, or 2048 MB on a free server. | ## SFTP | What | Limit | | --- | --- | | Port | `2022`. | | Sign-in | Your account password only. No SSH keys and no two-factor code. | | Suspended or locked server | Every sign-in is refused. | ## Free servers Free servers sleep when nobody plays, and leave out backups, databases, schedules and snapshot downloads. [How free servers work](/docs/managed-containers/free-servers/) lists every difference. ## Related - [Container Apps](/docs/managed-containers/) - [Rate limits](/docs/api/rate-limits/) - [Troubleshoot servers](/docs/managed-containers/troubleshooting/) # Troubleshoot servers > Fix the common problems with servers, from a server that will not start to errors in the console, files and API. Source: https://www.coritan.com/docs/managed-containers/troubleshooting/ Find the symptom or the message you see, then follow the fix. Messages appear here exactly as the dashboard or the API shows them, and each section links to the page that covers the task in full. ## The server will not start **Start** shows at the top of the server page while the server is stopped. When a start is refused, the dashboard shows `Could not send the power signal` with one of these reasons: `Cannot start while server status is installing` : The software is still installing. Wait until the **Console** tab shows the console, then start the server. The same message names `transferring` while we move the server to another machine, and `install_failed` when the install did not finish ([The install failed](#the-install-failed)). `Cannot send power actions while server is suspended` : The server is suspended. See [The server is suspended](#the-server-is-suspended). `Free servers must be started via the wake endpoint, not a direct power start/restart` : This is a free server. Select **Wake server** on the **Console** tab ([How free servers work](/docs/managed-containers/free-servers/)). `We are optimising this server's world right now. You can start it again in a moment.` : We are removing unused chunks from the world, and the server cannot start until that finishes. Try again shortly. `Another action is already in progress` : The server is still carrying out an earlier power action. Wait until the status badge settles, then try again. `The node does not have this server yet; it is being restored there. Try again in a moment.` : We are putting the server's files back on the machine it runs on. Try again in a moment. `The node is unreachable`, `The node could not complete this request` or `Power action failed` : We could not reach the machine the server runs on, or it did not carry out the start. Try again shortly, and [ask for help](#get-help) if it keeps happening. **Start** is missing : The server is running, starting or stopping, or it is locked ([The server is locked](#the-server-is-locked)). A free server that is asleep shows **Wake server** on the **Console** tab instead. The server starts, then stops by itself : The game stopped. See [The server crashed or stopped](#the-server-crashed-or-stopped). ## The install failed The **Container Apps** page shows `Install failed`, and the **Console** tab says `The install did not finish. Fix it or run it again to bring the console back.` We try an install up to 3 times before we mark it failed, and up to 6 times when we could not reach the machine. When the version you chose is no longer published, we install the newest published version instead. So `Install failed` means every attempt failed. The dashboard cannot run the first install again. Start a new install from the **Software** tab instead: 1. Open the server's **Software** tab. The **Server software** panel opens first. 2. Under **Choose the software to run**, select the entry marked **Current**, or other software. 3. Choose a version. **Latest** is the safest choice when a specific version failed. 4. Choose **Keep my files**, or **Start clean** to delete everything the failed install left. 5. Select **Change version** or **Switch software**. With **Start clean**, the button is **Wipe and install…**: type the server's name to confirm. When the job shows `Succeeded`, start the server. [Change the server software](/docs/managed-containers/software/) describes each choice. `Wings reported the install script failed, so the new software is not on disk.` : The new install failed too. The message ends with the last lines of the install's output, which often name the cause. When it happens again, [ask for help](#get-help): we can run the install again from our side. ## Players cannot connect Players join at the address that **Copy address** at the top of the server page copies, such as `203.0.113.10:10432`. When they cannot connect, check these in order: The server is not running : The status badge must show `Running`, and the console must have printed the game's `Done` line. A server that is still starting refuses players. A free server has its own address rules ([Joining a free server](/docs/managed-containers/free-servers/#joining-a-free-server)). The address has no port : The IP address is shared with other servers, so players must type the port too. Copy the whole address with **Copy address**. The primary port changed : After **Make primary…** on the **Ports** tab, the address has the new port. Send your players the new address ([Manage ports](/docs/managed-containers/ports/#make-another-port-primary)). The game refuses the server because of its version : The player's game runs another Minecraft version. Pick the server's version in the Minecraft launcher, or change the server's version on the **Software** tab. The game says the player is not white-listed : The server's whitelist is on. Send `whitelist add` followed by the player's name on the **Console** tab. Every player is refused through a join address : The join address has **PROXY protocol** on, and the server does not expect it. [Troubleshoot proxies and join addresses](/docs/proxies/troubleshooting/) explains the fix. ## The disk is full File changes fail with `The server is out of disk space.`, and the game cannot save. The **Disk** figure on the server page shows how much of the plan's disk is in use. 1. Open the **Files** tab and delete what the server does not need: old logs in `logs`, old reports in `crash-reports`, worlds you no longer play, and archives you made with **Compress…**, which go into the server root. 2. Restart the server once there is room again. Snapshots are kept in your account's snapshot storage, so deleting snapshots frees no disk on the server. For more room, [move to a larger plan](/docs/billing/change-plan/). ## SFTP refuses the connection SFTP signs in with your Coritan account password on port `2022`, with a username made of your email address, a full stop and the first eight characters of the server's ID. It does not accept SSH keys or a two-factor code, and it refuses every sign-in while the server is suspended or locked. [Connect with SFTP](/docs/managed-containers/sftp/#troubleshooting) covers each message a client shows. ## The server crashed or stopped The **Console** tab shows what the game printed before it stopped. The same output is in `logs/latest.log` on the **Files** tab, and a Minecraft crash leaves a report in `crash-reports`. A free server stopped when nobody was playing : A free server stops after 10 minutes without players by default, and sooner while its machine is busy. Select **Wake server**, or join from the game ([How free servers work](/docs/managed-containers/free-servers/)). The server ran out of memory : The server is stopped when it runs out of memory altogether. Lower **Java memory** to the default, or move to a larger plan ([Change Java settings](/docs/managed-containers/software/java-settings/)). A plugin or mod stops the server : Turn off the addon you added last on the **Installed addons** panel, then start the server ([Manage installed addons](/docs/managed-containers/software/installed-addons/)). A datapack stops the world loading : Select **Start in safe mode**, which starts the world on the vanilla datapack alone ([Start a server in safe mode](/docs/managed-containers/software/safe-mode/)). The world was saved by a newer Minecraft version : Minecraft cannot load a world saved by a newer version. Change the server back to the newer version, or [restore a snapshot](/docs/snapshots/restore-a-snapshot/) taken before the change. A schedule stopped it : A **Send a power signal** task sends **Stop**, **Restart** or **Kill**. Check **Run history** on the **Schedules** tab ([Schedule server tasks](/docs/managed-containers/schedules/)). ## The server is suspended The server page shows **This server is suspended**: `It is stopped, and you cannot start it until we lift the suspension. Check the plan's billing status or open a ticket.` The console is unavailable, SFTP refuses to sign in, and the server cannot take or restore snapshots. An unpaid invoice : Select **Open Billing** and pay the open invoice. Paying it resumes the service ([Failed payments and suspended services](/docs/billing/failed-payments/)). A free server nobody used for 14 days : By default, we take a snapshot of a free server and suspend it after 14 days without activity, and delete it after 21 days. [Ask for help](#get-help) to bring it back before then ([Free servers nobody uses](/docs/managed-containers/free-servers/#free-servers-nobody-uses)). Any other reason : [Ask for help](#get-help). The ticket or email we sent you says why. When we lift a suspension, we delete the snapshots we took of the server on our own, such as `Saved before removal for non-payment`, unless you locked them ([Snapshots and backups compared](/docs/snapshots/snapshots-and-backups/)). ## The server is locked The server page shows **This server is locked** with our message, and the API answers `423` with `reason` set to `server_locked`: `We have locked this server because activity on it broke our acceptable use policy, and our team is reviewing it. Your files have not been deleted. If you believe this is a mistake, reply to the support ticket we opened with this lock.` While it is locked, you can stop the server but not start it. You can read its files, but you cannot change them or its software, and SFTP refuses to sign in. Select **Open review ticket** to reply to the ticket, or **Open Support** when the banner has no ticket. ## Too many requests `Too many requests for this action. Please wait and try again.` : You repeated an action too often on this server, such as power actions or software installs. The API answers `429`, and its `Retry-After` header says how many seconds to wait. [Server limits and rate limits](/docs/managed-containers/limits/) lists each budget. `Connection limit reached. Close other console sessions first.` : You have 8 consoles open on this server, or 32 across all servers. Close console tabs you are not using. `Slow down` : You sent more than 20 commands in one second on one console. The console drops the extra commands. ## Errors from the API `401` `Invalid or expired token` : The access token has run out. [Refresh it](/docs/api/authentication/) and send the request again. `404` `Server not found` : No server has that UUID. Copy it with **Copy server ID** on the server page. `403` `Access denied` or `Insufficient permissions` : The server belongs to another account, and its owner has not shared it with you, or has not given you the permission this request needs ([Share a server with other users](/docs/managed-containers/users/)). `409` with `reason` set to `server_deleting` : `This server is being deleted.` Nothing more can be done with it. `422` on `POST /command` : The `command` query parameter is missing or longer than 1,000 characters. `502` or `503` with a message that starts `The node` : We could not reach the machine the server runs on, or it refused the request. Try again shortly. [Errors](/docs/api/errors/) explains the shape of every error response. ## Get help If none of these fixes the problem, [contact support from the dashboard](/docs/support/conversations/). Include: - The server's ID, which **Copy server ID** on the server page copies. - What you did, and the message you saw, exactly as it appeared. - The last lines of the console output, when the problem is with the game. ## Related - [Server limits and rate limits](/docs/managed-containers/limits/) - [Use the console and power controls](/docs/managed-containers/console/) - [Change the server software](/docs/managed-containers/software/) - [Troubleshoot proxies and join addresses](/docs/proxies/troubleshooting/) # Cloud Compute > Run KVM virtual machines with their own disk, public address and console, billed by the hour or the month. Source: https://www.coritan.com/docs/cloud-compute/ In the dashboard: - /solutions/cloud: https://www.coritan.com/solutions/cloud - /dashboard/compute: https://www.coritan.com/dashboard/compute Cloud Compute gives you a KVM virtual machine with its own disk, operating system, public IP address and display console. You choose the image it starts from, get full root access and install what you like. The dashboard calls each one an *instance*. Use it when you want to run the whole machine yourself. If you would rather we install and run the software for you, use [Container Apps](/docs/managed-containers/) instead. ## Your instances Select **Cloud Compute** in the [dashboard](https://www.coritan.com/dashboard/compute) sidebar to open its page. It lists every instance on your account, newest first. Each card shows the hostname, status, address, location and image, with rings for **CPU**, **Memory** and **Disk** use. A **Rescue** badge marks an instance that is booted into rescue media. - Filter the list with **All**, **Running**, **Stopped** or **Suspended**, or select a tag to see only the instances that carry it. **Clear** removes the tag filter. - Type in the search box to find an instance by name, address, image or location. - Select **Order instance** to open the [order page](/docs/cloud-compute/create-an-instance/), where you can also add floating IPs and a DDoS Shield profile to the order. Select an instance's hostname to open it. The header shows its status, image, plan, location and address, how long it has been up, and the power buttons. **Copy address** copies the address. The tabs below the header hold everything else, and each has a page here. The public page at `https://www.coritan.com/solutions/cloud` describes the product. The order page shows the plans, images and locations you can actually order. ## What you can do Get started: - [Create an instance](/docs/cloud-compute/create-an-instance/) - [Operating system images](/docs/cloud-compute/images/): what each image gives you and who you sign in as - [Connect to an instance over SSH](/docs/cloud-compute/connect-to-an-instance/) Run the instance: - [Start, stop and restart an instance](/docs/cloud-compute/power/) - [Use an instance's console](/docs/cloud-compute/console/), the **Console** tab - [Manage an instance's password, SSH keys and hostname](/docs/cloud-compute/access/), the **Access** tab - [Rebuild an instance](/docs/cloud-compute/rebuild/) onto a fresh image - [Repair an instance in rescue mode](/docs/cloud-compute/rescue-mode/), the **Rescue** tab Protect your data: - [Take and roll back instance snapshots](/docs/cloud-compute/snapshots/), the **Snapshots** tab - [Back up and restore an instance](/docs/cloud-compute/backups/), the **Backups** tab Network and usage: - [Manage an instance's addresses and names](/docs/cloud-compute/networking/), the **Network** tab - [How instance traffic is counted](/docs/cloud-compute/traffic/) - [Read an instance's graphs](/docs/cloud-compute/graphs/), the **Graphs** tab Plan and settings: - [Change an instance's plan or cancel it](/docs/cloud-compute/billing/), the **Billing** tab - [Rename and tag an instance](/docs/cloud-compute/settings/), the **Settings** tab Reference: - [Cloud Compute limits](/docs/cloud-compute/limits/) - [Troubleshoot instances](/docs/cloud-compute/troubleshooting/) ## How instances are billed Each instance is a service on the plan and billing cycle you chose when you ordered it. The plan sets the vCPU, memory, disk and monthly traffic allowance, and the order page shows its price for each cycle it sells. Hourly plans draw on your account credit as the instance runs, as [How hourly billing works](/docs/billing/hourly-billing/) explains. One public IPv4 address comes with each instance at no charge while it stays attached as the instance's primary address. More addresses and subnets are [Floating IPs](/docs/floating-ips/), billed as services of their own. [Billing](/docs/billing/) covers invoices, credit and payment. ## Limits - The size you order sets each instance's vCPU, memory, disk and monthly traffic allowance. It stays with the instance: for another size, move to a new instance, as [Get a different size](/docs/cloud-compute/billing/#get-a-different-size) describes. - An instance holds up to 8 snapshots and up to 16 backups. - Power actions, backups, restores and rebuilds are rate limited per instance. [Cloud Compute limits](/docs/cloud-compute/limits/) lists every limit. ## With the API `GET /api/v1/client/vps` lists the instances on your account, newest first. Add `?tag=` with one of your tags to list only the instances that carry it. ```bash curl https://api.coritan.com/api/v1/client/vps \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` Each item carries the instance's `uuid`, `hostname`, `status`, `power_state` and `ip_address`, its size (`cpu_cores`, `memory_mb`, `disk_gb`, `traffic_tb`), `template_name` for its image, `location_name`, and `tags`. It also carries the service behind it: `service_id`, `service_status`, `billing_cycle`, `next_due_date` and `product_name`. Use the `uuid` in every other instance request. `status` is one of `creating`, `running`, `stopped`, `suspended`, `error` or `migrating`. `power_state` is `running`, `stopped`, `paused` or `unknown`. `GET /api/v1/client/vps/{uuid}` returns one instance with the same fields, plus `rescue_mode` and `rescue_iso`, the image's `os_family`, `os_version` and `username`, and `migration`. `migration` is `null` unless we are moving the instance to another host, and most changes answer `409` while a move runs. ```bash curl https://api.coritan.com/api/v1/client/vps/$INSTANCE_UUID \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` An instance that is not on your account, or that has been cancelled and removed, answers `404` with `Cloud Compute instance not found`. ## API - `GET /api/v1/client/vps`: List all VPS instances owned by the current user (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-get-api-v1-client-vps) - `GET /api/v1/client/vps/{uuid}`: Get VPS instance details including template metadata (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-get-api-v1-client-vps-uuid) # Create an instance > Order a Cloud Compute instance on its order page, with a plan, an operating system, a location, your SSH keys and any add-ons. Source: https://www.coritan.com/docs/cloud-compute/create-an-instance/ In the dashboard: - /dashboard/order/cloud-compute: https://www.coritan.com/dashboard/order/cloud-compute The **Cloud Compute** order page asks five things, in numbered sections: **Plan**, **Operating system**, **Location**, **Settings** and **Add-ons**. The first four start with an answer chosen for you, and the summary beside them keeps the price up to date as you change them. Add-ons are other products bought in the same order, such as more floating IPs. We start building the instance once the first invoice is paid, and it boots with the image, hostname and SSH keys you chose. ## Before you begin - [Create a Coritan account](/docs/get-started/create-an-account/) and sign in. - Credit on your account pays the first invoice when you place the order. Without enough credit, you pay the rest by card or PayPal on the page the order opens. To pay in one go, [add credit](/docs/billing/add-credit/) first. - Hourly billing needs a minimum total of deposits on your account before you can choose it. [How hourly billing works](/docs/billing/hourly-billing/) explains the rule. - To sign in over SSH without a password, have your SSH public key to hand, such as the contents of `~/.ssh/id_ed25519.pub`. [Connect to an instance over SSH](/docs/cloud-compute/connect-to-an-instance/) shows how to make one. ## Choose a plan 1. In the [dashboard](https://www.coritan.com/dashboard/compute), go to **Cloud Compute** and select **Order instance**. You can also select the **Cloud Compute** card on the **Order a service** page. Either way, the order page opens at `https://www.coritan.com/dashboard/order/cloud-compute`. 2. At the top of **Plan**, choose how often to pay. The switch lists the billing cycles the plan sells, and a longer term names what it saves against paying monthly. 3. Choose a **Hardware tier**, if the page offers more than one. The tier sets the CPU class and the storage, and each tier sells the same sizes. 4. Under **Size**, choose a plan. Each row shows its price for the cycle, its vCPUs, memory, disk and monthly traffic. We start with the recommended plan, or else the smallest one on sale. A plan marked **Sold out** cannot be ordered anywhere right now. When you choose hourly billing, a note under the sizes explains that hourly services draw on your credit as they run. If your account has not deposited enough yet, the note names the deposit it still needs, and **Top up credit** takes you to Billing to add it. ## Choose the operating system and the location 1. Under **Operating system**, choose a card under **Distribution**. We choose its newest release that the plan can run, and a card under the distributions lists every release to choose from. - The line under the releases names the user you sign in as and the disk the image needs. - A release the plan is too small for is greyed out, with the reason under it, such as `Needs at least 20 GB of disk`. - A distribution marked **Plan too small** has no release that fits the plan. Choose a larger plan, or another distribution. 2. Under **Location**, choose a data centre on the map or in the list under **Data centres**, which groups them by region. We start with the one marked **Closest to you**. Pick the one closest to the people who will use the instance: its address is local to that location. A location marked **Sold out** has no room for this plan right now. You can rebuild the instance onto another image later, from its **Access** tab. ## Check the settings 1. Under **Settings**, keep the suggested **Hostname**, such as `ubuntu-fra-1`, or type your own, such as `web-1`. It takes letters, digits, hyphens and dots, up to 100 characters, with each part between dots at most 63 characters. The field turns capitals into lower case and drops any other character as you type. The instance uses it as its hostname. The **Access** tab accepts a hostname without dots only. If you order a name with dots, such as `web-1.example.com`, you can later replace it only with a single label, such as `web-1`. 2. Under **SSH keys**, paste one OpenSSH public key per line. Cloud-init adds them to the image's default user on the first boot. This is optional. 3. Leave the switch on the **Public IPv4 address** card on to order the included address. It costs nothing while it stays attached as the instance's primary address. We take it from the instance's location and attach it when setup finishes. 4. If the plan has **Plan options**, answer them. We add the price of any option you take to every charge. > [!IMPORTANT] > If you turn off the **Public IPv4 address** switch, the instance has no public address. You can reach it only through the [display console](/docs/cloud-compute/console/) until you [attach a floating IP](/docs/floating-ips/attach-and-detach/) to it. You also cannot add floating IPs or a DDoS Shield profile to the order. ## Add products to the order Under **Add-ons**, turn on the switch of anything you want to buy with the instance. This is optional. Each card shows its price for the order's billing cycle, and everything you add goes on the instance's first invoice: - **Floating IPs** are more public IPv4 addresses. Set **How many** you want, up to four in one order, and choose a size under **Size** when more than one is on sale. They come from the address pool in the instance's location, and we attach them to the instance when it is ready. - A **DDoS Shield profile** gives the instance's addresses your own protection mode, firewall rules and packet rate limits. It protects the included address and any floating IPs in the order. Give it a **Profile name**, or we name it after the hostname. Without one, the addresses keep the scrubbing every address on the platform gets. - **Mail Hosting** adds mailboxes on your own domain. Choose a plan if there is more than one, and add a **Domain** now or later. - **SMTP Relay** delivers the mail your applications send. Choose a plan if there is more than one, and add a **Sending domain** now or later. A card that cannot be added says why, and its switch stays off. Floating IPs and a DDoS Shield profile both need the included public IPv4, and floating IPs also need a location whose pool has addresses left. Each add-on becomes a service of its own, which you can cancel on its own later. [Add products to the order](/docs/get-started/order-a-service/#add-products-to-the-order) has the rules for every product. ## Place the order 1. Check the summary under **Your order**. On a phone it follows the sections, and **Review order** at the bottom of the screen takes you there. It lists the plan, the operating system, the location and the hostname, each with **Change** beside it to go back to its section. Under **Billed** it lists the plan, the public IPv4 address as **Included**, and each add-on with its price. 2. Check the **Total**. It shows the price for each billing cycle, any one-time setup fee, what a longer term saves and what is due today. The line under it says how the first invoice will be paid. 3. If the button is greyed out, read the line under it. It names the first answer still missing, such as `Choose an operating system.`, and selecting it takes you to that section. 4. Select the button. It reads **Deploy instance** when your credit pays the first invoice or your account is billed in arrears, and **Place order and pay** when you pay on the next page. If we refuse the order, **Could not place the order** appears above the button with the reason, and your choices stay as they were. Otherwise the order's own page opens. When its title reads **Order placed, payment due**, pay under **Pay invoice**, as [Pay and follow the order](/docs/get-started/order-a-service/#pay-and-follow-the-order) describes. If you leave without paying, pay the invoice under [Invoices](/docs/billing/invoices/). Setup starts once it is paid. ## Result The order's page shows **Setting up** while we build the instance, and checks on it every few seconds. Its title becomes **Your order is ready** when the instance and every add-on are active. The instance appears on the [Services](/docs/get-started/services/) page as soon as you order it, and on the **Cloud Compute** page once it has been built and started, with the included address attached. We email you when each service is ready. We generate a password for the default user when we build the instance, but no page shows it. The **Access** tab shows only whether one is set, although the order pages say you will find the password there. Sign in with the SSH key you added, or [reset the password](/docs/cloud-compute/access/#reset-the-password) to get one you can use. ## Troubleshooting The button under the summary is greyed out : The line under it names the first answer still missing. Select it to go to that section. `This plan has no active price.` : The plan has no price on sale on any billing cycle. Choose another plan. `This plan is sold out everywhere right now.` : No location has room for this plan. Choose another size or hardware tier. `Hourly billing needs a deposit first.` : The note under **Size** names the deposit your account still needs. Select **Top up credit** to add it, or choose another cycle. `That image cannot go on this plan: needs at least 20 GB of disk.` : You chose a smaller plan after the image. Choose a larger plan, or another release. **Could not load the images** : The list of images did not load. Select **Try again**. **No images are ready right now** : We are still preparing the images for the plan. Try again shortly. `That location has no capacity for this plan.` : The location filled up after you chose it. Choose another. **Every location is sold out for this plan** means you need a different size or tier. `Use letters, digits and hyphens; each part at most 63 characters.` : A part of the hostname is longer than 63 characters, or starts or ends with a hyphen. `A hostname cannot start or end with a dot or hyphen.` and `A hostname cannot contain consecutive dots.` name the other rules. `Each line must be one OpenSSH public key, e.g. "ssh-ed25519 AAAA… you@laptop".` : A line in **SSH keys** is not a public key. Paste the `.pub` file, one key per line. The page accepts `ssh-ed25519`, `ssh-rsa`, `ssh-dss`, `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, `ecdsa-sha2-nistp521`, `sk-ssh-ed25519@openssh.com` and `sk-ecdsa-sha2-nistp256@openssh.com` keys. The **Floating IPs** card says `Keep the included public IPv4 to add floating IPs.` : Turn the switch on the **Public IPv4 address** card back on. The included address is free, and floating IPs are extra addresses on top of it. The **Floating IPs** card says the pool is sold out : The location has no addresses left to sell. Choose another location, or order the instance without them and [order a floating IP](/docs/floating-ips/order-a-floating-ip/) later. **No address** : We built the instance but could not attach the address ordered with it. The address is still on your account: go to **Floating IPs**, open it, and attach it to the instance as [Attach and detach a floating IP](/docs/floating-ips/attach-and-detach/) describes. ## Related - [Operating system images](/docs/cloud-compute/images/) lists what the images have in common and how they differ. - [Connect to an instance over SSH](/docs/cloud-compute/connect-to-an-instance/) is the next step once the instance runs. - [Order a service](/docs/get-started/order-a-service/) explains ordering and payment for every product. - [Data centres and locations](/docs/platform/data-centres/) describes each location. ## With the API Three requests give you the values an order needs: - `GET /api/v1/products/` lists the plans. A Cloud Compute plan has `module_name` set to `vps`. Its `pricing` list holds one row per billing cycle, each with the `id` you send as `pricing_id`, and its `locations` list gives each location's `code` and whether it is `orderable` for the plan. - `GET /api/v1/client/vps/templates` lists the images you can order, each with the `id` you send as `template_id`. [Operating system images](/docs/cloud-compute/images/#with-the-api) describes the fields. - `GET /api/v1/locations` lists every active location with its `code`. ```bash curl "https://api.coritan.com/api/v1/products/?per_page=200" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` Place the order with `POST /api/v1/services/order`, as [Order a service](/docs/get-started/order-a-service/#with-the-api) describes, with the plan's `product_id` and the cycle's `pricing_id`. Send the instance's settings in `config`: `location` : The location code, such as `fra`. `template_id` : The image's `id`. `hostname` : The instance's hostname. When you leave it out of `config`, we use the top-level `hostname`. `ssh_keys` : Optional. OpenSSH public keys, one per line. `order_ipv4` : Optional, `false` when left out. Send `true` to order the included public IPv4 address with the instance, as the dashboard does. `pool_id` : Optional. The IP pool to take the included address from. Without it we use a pool in the instance's location. [IP pools and regions](/docs/floating-ips/pools/) lists them. `allow_pool_fallback` : Optional, `false` when left out. Send `true` to accept an address from a pool in another region when the instance's location has none, or when the `pool_id` you sent is in another region. `prefix_len` : Optional, `32` when left out. `32` orders one address. `24` to `29` order a subnet instead, which is billed at its catalogue price and is accepted only where subnets are on sale. [How subnets work](/docs/floating-ips/subnets/) explains them. The plan sets the instance's size, so leave `cpu_cores`, `memory_mb` and `disk_gb` out. To buy add-ons in the same order, list them in `addons`, as [Add-ons in the order](/docs/get-started/order-a-service/#add-ons-in-the-order) describes. An instance takes up to 4 floating IPs, and only with `"order_ipv4": true`. A DDoS Shield profile protects the included address and the floating IPs in the order. This example adds one floating IP: ```bash curl -X POST https://api.coritan.com/api/v1/services/order \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "product_id": 12, "pricing_id": 34, "hostname": "web-1", "config": { "location": "fra", "template_id": 7, "hostname": "web-1", "order_ipv4": true, "ssh_keys": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExample alex@example.com" }, "addons": [ {"product_id": 31, "quantity": 1} ] }' ``` The response answers `201` with the new `service`, a service for each add-on in `addons`, `invoice_id`, `requires_payment`, `amount_due` and a `message`. When `requires_payment` is `true`, pay the invoice it names before setup starts. Once the instance exists, `GET /api/v1/client/vps` lists it with its `uuid`. A second identical order (same plan, cycle and hostname) within two minutes of the first, while the first is still pending, returns the first order with the message `Order already submitted` instead of a new one. Errors in the order answer `422` with a list under `detail.errors`: - `template_id is required`, or `location is required (airport code, e.g. iad)`. - `OS template 7 is not ready in location 'fra'`: that image cannot be built in that location. Choose another image or location. - `No available ... with sufficient capacity for this Cloud Compute instance`: the location has no room for the plan. - `No sellable IPv4 pool available in location 'fra'`: we have no addresses to sell in that location. Send `allow_pool_fallback`, order without `order_ipv4`, or add a floating IP later. - `IP pool region 'iad' does not match compute location 'fra' (set allow_pool_fallback to override)`: the `pool_id` you sent is in another region. - `Keep the included public IPv4 to add floating IPs. It is free, and floating IPs are extra addresses on top of it.`: the order has floating IPs in `addons` without `"order_ipv4": true`. - `An instance takes at most 4 floating IPs in one order.`: the `quantity` of floating IPs is more than 4. An hourly cycle before your account holds the deposit answers `403` with `You must deposit at least $10 before using hourly billing services`. ## API - `POST /api/v1/services/order`: Order a platform service, and any add-ons bought with it (https://www.coritan.com/docs/api/reference/client/services/#op-post-api-v1-services-order) # Operating system images > The operating system images an instance can run, and how to list the ones available to you. Source: https://www.coritan.com/docs/cloud-compute/images/ An *image* is the operating system an instance starts from. You choose one in the **Operating system** section of the order page when you [create an instance](/docs/cloud-compute/create-an-instance/), and you can [rebuild](/docs/cloud-compute/rebuild/) onto another later. The API calls an image a *template*. ## Which images you can order The order page lists every image that is ready to build, grouped by distribution with the newest release first. The images on offer change over time, so the order page and `GET /api/v1/client/vps/templates` are the list to trust. | An image whose slug starts with | Is listed under | | --- | --- | | `ubuntu` | Ubuntu | | `debian` | Debian | | `almalinux` | AlmaLinux | | `rocky` | Rocky Linux | | `fedora` | Fedora | | `centos` | CentOS Stream | | `opensuse` | openSUSE | | `arch` | Arch Linux | | `alpine` | Alpine Linux | | `freebsd` | FreeBSD | | `windows` | Windows Server | An image with any other slug is listed after these, under its own name. ## What the first boot sets up We hand these settings to cloud-init in the image when we build the instance: Default user : The image's own default account, such as `root` or the distribution's usual user. The order page shows it as "Sign in as" and the **Access** tab shows it as **Username**. Password : A random password for the default user. We store it but do not show it. [Reset the password](/docs/cloud-compute/access/#reset-the-password) to get one you can read. SSH keys : The public keys you pasted in the **Settings** section of the order page, added to the default user's `authorized_keys`. Hostname : The hostname you chose in the **Settings** section. Network : The instance's primary address, gateway and mask, once an address is attached. DNS resolvers : `1.1.1.1` and `8.8.8.8`. The dashboard also talks to the QEMU guest agent inside the instance, to read its operating system and addresses and to reset its password. The **Console** tab shows **Agent running** when the agent answers. ## Minimum sizes An image can need a minimum disk and memory. The **Operating system** section of the order page shows a release the plan is too small for, with the reason under it, and does not let you pick it: | Reason on the order page | What it means | | --- | --- | | `Needs at least 20 GB of disk` | The plan's disk is smaller than the image needs. | | `Needs at least 2 GB of memory` | The plan's memory is smaller than the image needs. | | **Plan too small** | No release of the distribution fits the plan. | ## With the API `GET /api/v1/client/vps/templates` lists the images you can order or rebuild onto: every enabled image that at least one of our hosts can build now. ```bash curl https://api.coritan.com/api/v1/client/vps/templates \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json [ { "id": 7, "name": "Ubuntu 24.04 LTS", "slug": "ubuntu-24-04", "os_family": "linux", "os_version": "24.04", "default_username": "ubuntu", "min_cpu": 1, "min_memory_mb": 1024, "min_disk_gb": 10, "ready_nodes": 3 } ] ``` `id` : The value to send as `template_id` when you order or rebuild. `name`, `slug` : The image's name and its short identifier. The slug's first part names the distribution. `os_family` : `linux`, `windows` or `bsd`. `os_version` : The release, such as `24.04`. `default_username` : The user cloud-init sets up and the one you sign in as. `min_cpu`, `min_memory_mb`, `min_disk_gb` : The smallest plan the image runs on. `ready_nodes` : How many of our hosts can build the image now. An image that is ready somewhere may still not be ready in the location you want; an order for it there answers `422` with `OS template 7 is not ready in location 'fra'`. ## API - `GET /api/v1/client/vps/templates`: OS templates ready for order/rebuild: enabled and ready on at least one active node (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-get-api-v1-client-vps-templates) # Connect to an instance over SSH > Find an instance's address and user name, and sign in over SSH with your key or a password. Source: https://www.coritan.com/docs/cloud-compute/connect-to-an-instance/ Sign in to an instance over SSH to run commands on it. You connect as the image's default user, with the SSH key you added when you ordered or with a password you reset. The commands below work in a terminal on Linux and macOS, and in PowerShell on Windows 10 and later. ## Before you begin - The instance is running and has a public address. The header shows it, and **Copy address** copies it. An instance that shows **No address** needs a [floating IP](/docs/floating-ips/attach-and-detach/) first. - You know the user to sign in as. Open the instance's **Access** tab: the **User account** card shows it as **Username**. - You have an SSH key pair whose public key is on the instance, or a password for the user. ## Make an SSH key Skip this if you already have a key in `~/.ssh`. 1. Create a key pair: ```bash ssh-keygen -t ed25519 -C "alex@example.com" ``` Press Enter to keep the default file, and choose a passphrase when asked. 2. Print the public key and copy the whole line: ```bash cat ~/.ssh/id_ed25519.pub ``` 3. Paste it under **SSH keys** when you [order an instance](/docs/cloud-compute/create-an-instance/). For an instance you already have, paste it on the **Access** tab and restart the instance, as [Manage SSH keys](/docs/cloud-compute/access/#manage-ssh-keys) describes. ## Sign in with your key 1. In the [dashboard](https://www.coritan.com/dashboard/compute), go to **Cloud Compute** and open the instance. Select **Copy address** in the header. 2. Connect as the user the **Access** tab names, with the address you copied: ```bash ssh ubuntu@203.0.113.10 ``` 3. The first time, SSH asks you to confirm the host's fingerprint. Type `yes`. ## Sign in with a password 1. Open the instance's **Access** tab and [reset the password](/docs/cloud-compute/access/#reset-the-password). The instance must be running. Copy the new password; we show it once. 2. Connect with `ssh` as above and type the password when asked. Many cloud images refuse password sign-in over SSH. If yours does, sign in on the [display console](/docs/cloud-compute/console/) with the password instead, and add your key to `~/.ssh/authorized_keys` from there. ## Result Your terminal shows the instance's shell prompt. Run `hostname` to check that you are on the right instance. ## Troubleshooting `Permission denied (publickey)` : The instance does not hold the key you used, or you signed in as the wrong user. Check the user on the **Access** tab. Keys saved on the **Access** tab apply only after the instance boots again. A [rebuild](/docs/cloud-compute/rebuild/) does not put your keys back, so save them again and restart. `Connection timed out` or `No route to host` : The instance is stopped, has no public address, or has not picked up an address you attached. [Start or restart it](/docs/cloud-compute/power/) from the dashboard header, which boots it with its current network settings. If the address is a floating IP, check the rules on its [**Shield** tab](/docs/floating-ips/shield-settings/). `Connection refused` : The address answers, but no SSH server listens on port 22. Sign in on the [display console](/docs/cloud-compute/console/) and start the SSH service, or check which port it uses. `WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!` : The instance has new SSH host keys. A rebuild creates new ones, and cloud-init usually does too on the first boot after you change the instance's keys, password, hostname or network settings. If you made one of those changes, remove the old entry and connect again: ```bash ssh-keygen -R 203.0.113.10 ``` ## Related - [Manage an instance's password, SSH keys and hostname](/docs/cloud-compute/access/) - [Use an instance's console](/docs/cloud-compute/console/) when SSH cannot reach the instance - [Repair an instance in rescue mode](/docs/cloud-compute/rescue-mode/) when it no longer boots # Start, stop and restart an instance > Use the power controls to start, restart, shut down or force off an instance, and read its power state. Source: https://www.coritan.com/docs/cloud-compute/power/ The power buttons in an instance's header start it, restart it and shut it down, as the power button on a physical machine would. Use them when the instance does not answer, after you change its settings, or to stop it while you do not need it. Stopping an instance does not stop its billing. A stopped instance keeps its disk, addresses and plan, and you pay for it until you [cancel it](/docs/cloud-compute/billing/). ## Before you begin - The instance is not suspended. A suspended instance shows **This instance is suspended** and no power buttons; select **Open Billing** to see why and settle it. ## Use the power buttons In the [dashboard](https://www.coritan.com/dashboard/compute), go to **Cloud Compute** and open the instance. The header shows the buttons that fit its state: **Start** : Shown when the instance is stopped. Boots it. **Restart** : Shown when it is running. Asks the operating system to shut down, then starts the instance again. **Stop** : Shown when it is running. Asks the operating system to shut down cleanly. If it has not stopped after 60 seconds, we power it off. **Kill…** : In the **Instance actions** menu beside the buttons, when the instance is running. Cuts the power at once. A message confirms each action: `Starting.`, `Rebooting.`, `Shutting down.` or `Powering off.`. The status in the header changes a few seconds later. > [!WARNING] > **Kill…** gives the operating system no chance to shut down, and you lose anything it has not written to disk. Use **Stop** unless the instance ignores it. To kill an instance, open the **Instance actions** menu, select **Kill…**, then select **Kill instance** in the dialog. ## Restart from the dashboard to apply changes A restart from the header stops the virtual machine and starts it again, and the new start picks up changes to its configuration: SSH keys, hostname, network settings and the display. A `reboot` typed inside the instance restarts only the operating system, so those changes wait for the next start from the dashboard. ## Result The header shows the new status: `running` with the time it has been up, or `stopped`. The **Cloud Compute** list shows the same status on the instance's card. ## Troubleshooting **Stop** or **Restart** does nothing : The operating system ignored the request to shut down. **Stop** powers the instance off after 60 seconds anyway. If a restart hangs, select **Kill…**, then **Start**. `Cannot perform power actions on a suspended instance` : The service behind the instance is suspended. Settle it from the **Billing** tab; [What happens when a payment fails](/docs/billing/failed-payments/) explains suspension. `Instance is migrating` : We are moving the instance to another host. Wait until the move finishes and try again. `Too many requests for this action. Please wait and try again.` : You sent more than 24 power actions for this instance in 60 seconds. Wait for the time the response gives in `Retry-After`. ## Related - [Use an instance's console](/docs/cloud-compute/console/) to watch the instance boot. - [Repair an instance in rescue mode](/docs/cloud-compute/rescue-mode/) when it does not boot. - [Cloud Compute limits](/docs/cloud-compute/limits/) lists every rate limit. ## With the API `POST /api/v1/client/vps/{uuid}/power` sends a power action. Send `action` as one of: `start` : Boot the instance. `reboot` : Shut the operating system down and start it again. The dashboard's **Restart**. `shutdown` : Shut down cleanly, and power off after 60 seconds if it has not stopped. The dashboard's **Stop**. `stop` : Power off at once. The dashboard's **Kill…**. ```bash curl -X POST https://api.coritan.com/api/v1/client/vps/$INSTANCE_UUID/power \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"action": "shutdown"}' ``` The request returns once the host has carried out the action, with `{"status": "ok", "action": "shutdown"}`. Any other word answers `400` with `Invalid action. Must be: start, stop, shutdown, reboot`. A suspended instance answers `403`, one we are moving answers `409`, and more than 24 actions in 60 seconds answer `429`, as [Rate limits](/docs/api/rate-limits/) describes. `GET /api/v1/client/vps/{uuid}/status` reads the instance's live state from its host. ```bash curl https://api.coritan.com/api/v1/client/vps/$INSTANCE_UUID/status \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` `status` : `running` or `stopped` as the host reports it, or `migrating` while we move the instance. `power_state` : The machine's own state, such as `running`, `paused` or `stopped`. `cpu` : CPU use as a fraction of the instance's vCPUs, from `0` to `1`. `mem`, `maxmem` : Memory in use and memory size, in bytes. `disk`, `maxdisk` : Disk in use and disk size, in bytes. `netin`, `netout` : Bytes received and sent since the instance last started. `uptime` : Seconds since the instance last started. `agent_online` : `true` when the QEMU guest agent inside the instance answered. `instance_status`, `rescue_mode`, `migration` : The instance's status on your account, whether it is booted into rescue media, and the move in progress, if any. ## API - `POST /api/v1/client/vps/{uuid}/power`: Execute a power action on the VPS (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-post-api-v1-client-vps-uuid-power) - `GET /api/v1/client/vps/{uuid}/status`: Get real-time VM status from Proxmox (enriched) (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-get-api-v1-client-vps-uuid-status) # Use an instance's console > Open a browser console on an instance's screen, even when its network is down, and read its hardware and recent tasks. Source: https://www.coritan.com/docs/cloud-compute/console/ In the dashboard: - /dashboard/compute/…/console: https://www.coritan.com/dashboard/compute The **Console** tab shows the instance's screen in your browser and sends it your keyboard and mouse, as a monitor and keyboard plugged into the machine would. It works while the instance has no network. Use it when SSH does not answer, for example after a firewall change locks you out, or to watch the instance boot. Below the screen, the tab shows what the instance is using, what its guest agent reports, its virtual hardware and the tasks its host ran for it. ## Before you begin - The instance is running. Start it from the header if it is stopped, as [Start, stop and restart an instance](/docs/cloud-compute/power/) describes. - The instance is not suspended. The console does not connect to a suspended instance. - To sign in at the screen, you need the password of the instance's user account. We do not show the password the instance was created with, so [reset the password](/docs/cloud-compute/access/#reset-the-password) first if you do not have one. SSH keys do not work at the console. ## Open the console 1. In the [dashboard](https://www.coritan.com/dashboard/compute), go to **Cloud Compute** and open the instance. 2. The **Console** tab opens first. The **Display** panel connects on its own and shows the screen once its status reads **Connected**. 3. Click the screen, then type. Your keys go to the instance. ## Use the display toolbar **Ctrl+Alt+Del** : Sends that key combination to the instance. Your own computer keeps it for itself when you press it, so use this button instead. **View only** : Stops your keyboard and mouse reaching the instance, so you can watch the screen without typing into it by accident. Select it again to type. **Reconnect** : Starts a new session and connects again. It reads **New session** while you are connected. **Fullscreen** : Fills the browser window with the screen. Select **Exit fullscreen**, or press Esc, to go back. > [!NOTE] > Some older instances still use a serial port as their display, and the console shows a note about it above the screen. Restart the instance once from the header, and the console shows its screen from then on. A `reboot` typed inside the instance does not change its display. ## Read the rest of the tab The four cards above the display show the instance's size and use: **vCPU** : The number of cores, and the share of them in use. **Memory** : The memory size, and the share in use. **Disk** : The disk size, and the share the host reports as allocated. **Bandwidth** : The traffic used this period against the plan's allowance, and the date it resets. [How instance traffic is counted](/docs/cloud-compute/traffic/) explains the figure. The **Guest** card shows what the QEMU guest agent inside the instance reports: its operating system, kernel and the addresses it sees. The badge reads **Agent running** when the agent answers, and **Agent offline** when it does not. You need the agent to [reset the password](/docs/cloud-compute/access/#reset-the-password), and it lets the instance shut down cleanly when you select **Stop**. The **Configuration** card shows the virtual hardware: CPU model, storage, host location, template, VM ID, boot order, each disk and network card, and the network settings and user account that cloud-init applies. The password and SSH keys never appear there. The **Recent tasks** table lists the last eight tasks the host ran for the instance, such as power actions, backups, snapshots and console sessions. **Status** reads `OK` when a task finished, `Running` while it runs, or the error the host gave. Select the refresh button (**Refresh tasks**) to read the list again. ## Result You see the instance's screen, and you can sign in at it as you would at a physical machine. ## Troubleshooting `The instance is powered off` : The instance is stopped. Select **Start** in the header. The console connects once the instance is running. `Could not connect to the console` : The session expired before it connected, or the host did not answer. Select **Try again** for a new session. If the message below it says `You are signed out. Sign in again to open the console.`, sign in to the dashboard again first. `Disconnected` : The session ended or the connection dropped. Select **Try again** for a new session. `The guest agent is not responding` : The instance has no QEMU guest agent, or it is not running. Install it from the console or over SSH. On Ubuntu or Debian, run `sudo apt install qemu-guest-agent`. On AlmaLinux, Rocky Linux or Fedora, run `sudo dnf install qemu-guest-agent`. Then run `sudo systemctl start qemu-guest-agent`. `No public address` : The instance has no public IP address, so you can reach it only through this console. Select **Open Network** and attach a floating IP, as [Manage an instance's addresses and names](/docs/cloud-compute/networking/) describes. ## Related - [Connect to an instance over SSH](/docs/cloud-compute/connect-to-an-instance/) once it is on the network. - [Repair an instance in rescue mode](/docs/cloud-compute/rescue-mode/) when it does not boot at all. - [Manage an instance's password, SSH keys and hostname](/docs/cloud-compute/access/) to reset the password or change the keys. ## With the API `GET /api/v1/client/vps/{uuid}/console` starts a console session on the instance's host. ```bash curl https://api.coritan.com/api/v1/client/vps/$INSTANCE_UUID/console \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "vnc_ws": "/ws/vps/vnc/5b0c2f7e-8a31-4d2b-9f4e-2a7c1d9e6b10", "vnc_session": "q3Vx…", "vnc_password": "Zk2p…", "expires_in": 120, "display": "vga" } ``` Within `expires_in` seconds, connect a VNC client that speaks WebSocket, such as noVNC, to the socket below. Pass your access token and the session in the query string, and answer the VNC password prompt with `vnc_password`. ```text wss://api.coritan.com/ws/vps/vnc/?token=&session= ``` Ask for a new session for every connection, as the dashboard does. `display` is `serial` while the instance still uses its serial port as its screen; restart it with the `reboot` power action to give it a graphics display. When the socket cannot connect, it closes with one of these codes: `4001` : The token is not a valid access token. `4003` : The instance is not on your account, or it is suspended or being moved. `4004` : The session is missing or expired, or the host's console is not available. Ask for a new session. `4029` : You already have too many consoles open. Close one and try again. When the host cannot start a console, `GET /console` answers `400` with a message that starts `Console unavailable`. `GET /api/v1/client/vps/{uuid}/config` returns the virtual machine's configuration with the password and SSH keys removed. `config` holds `cores`, `memory` (in MiB), `name`, `ostype`, `boot`, `agent`, `disks` and `nics` (keyed by device, such as `scsi0` or `net0`), and `cloud_init`, the settings cloud-init applies, such as `ipconfig0`, `ciuser` and `nameserver`. The response also carries `vmid`, `rescue_mode` and `rescue_iso`. `GET /api/v1/client/vps/{uuid}/guest` asks the guest agent. `agent_online` is `true` when it answered, and `os_info` and `network_interfaces` then hold what it reported. When a question fails, `agent_error`, `os_error` or `network_error` says why, such as `VM is not running` or `qemu-guest-agent not responding`. `GET /api/v1/client/vps/{uuid}/tasks` returns the host's recent tasks for the instance. Send `limit` for how many: the default is 50 and the most is 100. `type` : What the task did, such as `qmstart`, `qmshutdown`, `qmsnapshot`, `vzdump` for a backup or `vncproxy` for a console session. `status` : `OK` when the task finished cleanly, and the error when it failed. It is absent while the task runs. `starttime`, `endtime` : When the task started and finished, in Unix seconds. `upid` : The host's ID for the task. While we move the instance to another host, `/config`, `/guest` and `/tasks` answer `409` with `Instance is migrating`. ## API - `GET /api/v1/client/vps/{uuid}/console`: Mint a VNC console session (WebSocket path + RFB password) (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-get-api-v1-client-vps-uuid-console) - `GET /api/v1/client/vps/{uuid}/config`: Get instance config (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-get-api-v1-client-vps-uuid-config) - `GET /api/v1/client/vps/{uuid}/guest`: Get guest info (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-get-api-v1-client-vps-uuid-guest) - `GET /api/v1/client/vps/{uuid}/tasks`: Get instance tasks (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-get-api-v1-client-vps-uuid-tasks) # Manage an instance's password, SSH keys and hostname > Reset an instance's password, give it SSH keys, set its hostname and rerun cloud-init from the Access tab. Source: https://www.coritan.com/docs/cloud-compute/access/ In the dashboard: - /dashboard/compute/…/access: https://www.coritan.com/dashboard/compute The **Access** tab holds what you need to get into an instance: its user account and password, the SSH keys it receives, its hostname and its cloud-init settings. The tab also holds **Rebuild operating system**, which [Rebuild an instance](/docs/cloud-compute/rebuild/) describes. Each instance boots with a *cloud-init* drive. It holds the user account, password, SSH keys, hostname and network settings we keep for the instance, and cloud-init inside the operating system applies them as it boots. Most changes on this tab update that drive, so they reach the operating system the next time you start or restart the instance from the dashboard. A password reset is the exception: it works at once. ## Before you begin - The instance is running, to reset the password or regenerate cloud-init. Both buttons stay disabled while it is stopped. - The instance is not suspended or being moved to another host. - To reset the password, the QEMU guest agent runs inside the instance. The **Guest** card on the [**Console** tab](/docs/cloud-compute/console/) shows **Agent running** when it does. ## Find the user account In the [dashboard](https://www.coritan.com/dashboard/compute), go to **Cloud Compute**, open the instance and select the **Access** tab. The **User account** card shows the account the image creates. **Username** is the name to sign in with; **Copy username** copies it. **Password** reads **Set** when we hold a password for the account. We store it encrypted and never show it again after we set it, including the one the instance was created with. ## Reset the password 1. On the **User account** card, select **Reset password…**. 2. Select **Reset password** in the dialog. 3. The card shows the **New password**. Select **Reveal** to read it, or **Copy all** to copy the username and password together. 4. Store the password somewhere safe, then select **I have saved them**. We set a new random password for the account inside the running instance, and cloud-init keeps it when the instance next boots. The old password stops working at once. SSH keys keep working. > [!IMPORTANT] > We show the new password once. If you lose it, reset it again. ## Manage SSH keys The **SSH keys** card sets the public keys cloud-init adds to the user account's `~/.ssh/authorized_keys`. 1. Under **Public keys**, paste every key you want the instance to receive, one per line. Lines that start with `#` are ignored. 2. Select the save button. It counts the keys as you paste, such as "Save 2 keys". 3. Restart the instance with **Restart** in the header. Cloud-init adds the keys as the instance boots. We cannot read the keys back, so the box is empty each time you open the tab. Saving replaces the whole list we hold, so paste every key you want each time. A [rebuild](/docs/cloud-compute/rebuild/) clears the list: save your keys again after one, then restart. > [!WARNING] > Cloud-init only adds keys. Keys already in `~/.ssh/authorized_keys` stay there when you save a shorter list, and **Remove all keys…** takes no key off the instance. To stop a key from signing in, delete its line from `~/.ssh/authorized_keys` inside the instance. ## Change the hostname 1. On the **Hostname** card, type the new name under **Hostname**. Use letters, digits and hyphens, up to 63 characters, with no hyphen at the start or end. Dots are not allowed here. 2. To apply it straight away, tick **Reboot now so the guest picks it up**. The box is disabled while the instance is stopped; it takes the name when it starts. 3. Select **Save hostname**. The dashboard and the host use the new name at once. Inside the instance, cloud-init sets it as the hostname on the next boot. The **Name** card on the **Settings** tab and **Edit hostname…** in the header's **Instance actions** menu change the same name. The hostname does not create a DNS record. To reach the instance by a name in your own domain, see [Manage an instance's addresses and names](/docs/cloud-compute/networking/). ## Regenerate cloud-init Regenerate cloud-init when the instance did not pick up a change, such as a primary address you attached. 1. On the **Cloud-init** card, select **Regenerate cloud-init…**. 2. Select **Regenerate cloud-init** in the dialog. The message that follows names the network settings it wrote, such as `ip=203.0.113.10/24 gw=203.0.113.1`. 3. Restart the instance with **Restart** in the header. Regenerating writes the user account, the password we hold, the hostname, the DNS resolvers and the instance's primary address into its cloud-init settings again. It leaves the SSH keys as you last saved them. ## Result The **User account** card shows **Password** as **Set**, and the instance accepts the new password straight away. After the next restart from the header, the instance has your SSH keys, its new hostname and its current network settings. ## Troubleshooting `Cloud Compute instance must be running to reset password` : Start the instance from the header, wait until it reads `running`, and reset again. `Could not reset the password: Internal server error` : The guest agent inside the instance did not answer. Check the **Guest** card on the **Console** tab. If it shows **Agent offline**, sign in with an SSH key and install the agent, as [Use an instance's console](/docs/cloud-compute/console/#troubleshooting) describes. Without a key or a password, set one from [rescue mode](/docs/cloud-compute/rescue-mode/). `Could not save the SSH keys` : Add the key from inside the instance instead. Sign in on the [console](/docs/cloud-compute/console/) or over SSH, and append the key as a new line to `~/.ssh/authorized_keys`. `This is a private key. Paste the public key (.pub) instead.` : You pasted the private half of the key pair. Paste the contents of the file that ends in `.pub`. `Not an OpenSSH public key (expected "ssh-ed25519 AAAA… comment").` : The line is not a public key in OpenSSH format. Keys from PuTTYgen need converting: copy the key from the box headed "Public key for pasting into OpenSSH authorized_keys file" in PuTTYgen. `Letters, digits and dashes only; up to 63 characters; no leading or trailing dash.` : The hostname has a dot, a space or another character it cannot hold, or it is too long. Use one label, such as `web-2`. `Permission denied (publickey)` after you saved keys : The instance has not booted since you saved them. Restart it from the header; a `reboot` typed inside the instance does not apply the keys. `WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!` : Cloud-init usually creates new SSH host keys on the first boot after you change the instance's keys, password, hostname or network settings. Remove the old entry with `ssh-keygen -R 203.0.113.10` and connect again. ## Related - [Connect to an instance over SSH](/docs/cloud-compute/connect-to-an-instance/) - [Use an instance's console](/docs/cloud-compute/console/) to sign in with the password when SSH does not answer. - [Rebuild an instance](/docs/cloud-compute/rebuild/) to start again from a fresh image. ## With the API `GET /api/v1/client/vps/{uuid}/credentials` returns the account without its password: ```json { "username": "ubuntu", "password_stored": true, "updated_at": "2026-09-16T10:42:00" } ``` `updated_at` is when the instance's record last changed, for any reason. `POST /api/v1/client/vps/{uuid}/reset-password` sets a new random password and returns it once, as `{"password": "…"}`. It needs no body. An instance that is not running answers `400` with `Cloud Compute instance must be running to reset password`. ```bash curl -X POST https://api.coritan.com/api/v1/client/vps/$INSTANCE_UUID/reset-password \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` `PUT /api/v1/client/vps/{uuid}/ssh-keys` replaces the keys we hold. Send `ssh_keys` as one string with a key on each line; an empty string clears the list. The response is `{"status": "ok", "keys_set": true}`, with `keys_set` `false` after clearing. ```bash curl -X PUT https://api.coritan.com/api/v1/client/vps/$INSTANCE_UUID/ssh-keys \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"ssh_keys": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExample alex@example.com"}' ``` `PATCH /api/v1/client/vps/{uuid}/hostname` sets the hostname. Send `hostname`, and `reboot` as `true` to restart a running instance straight away; it is `false` when left out. The response is `{"hostname": "web-2", "rebooted": true}`, where `rebooted` repeats what you sent. A name the rules above refuse answers `400` with `Invalid hostname`. ```bash curl -X PATCH https://api.coritan.com/api/v1/client/vps/$INSTANCE_UUID/hostname \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"hostname": "web-2", "reboot": true}' ``` `POST /api/v1/client/vps/{uuid}/cloud-init/regenerate` rewrites the cloud-init settings and answers `{"status": "regenerated", "ipconfig0": "ip=203.0.113.10/24,gw=203.0.113.1"}`. `ipconfig0` is `null` when the instance has no address attached. Restart the instance afterwards with the `reboot` [power action](/docs/cloud-compute/power/#with-the-api). A suspended instance answers `403` with `Instance is suspended` to `ssh-keys`, `hostname` and `cloud-init/regenerate`. While we move the instance to another host, every request that changes it answers `409` with `Instance is migrating`. ## API - `GET /api/v1/client/vps/{uuid}/credentials`: Username + whether a password is stored (no plaintext) (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-get-api-v1-client-vps-uuid-credentials) - `POST /api/v1/client/vps/{uuid}/reset-password`: Reset the root/admin password (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-post-api-v1-client-vps-uuid-reset-password) - `PUT /api/v1/client/vps/{uuid}/ssh-keys`: Put SSH keys (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-put-api-v1-client-vps-uuid-ssh-keys) - `PATCH /api/v1/client/vps/{uuid}/hostname`: Patch hostname (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-patch-api-v1-client-vps-uuid-hostname) - `POST /api/v1/client/vps/{uuid}/cloud-init/regenerate`: Regenerate cloud init (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-post-api-v1-client-vps-uuid-cloud-init-regenerate) # Rebuild an instance > Erase an instance's disk and reinstall it from an operating system image, keeping its addresses and plan. Source: https://www.coritan.com/docs/cloud-compute/rebuild/ A rebuild erases the instance's disk and installs an operating system image on it from scratch. Use it to start again with a clean system, or to move to another distribution or release. The instance keeps its plan, hostname and IP addresses, and its billing does not change. ## What a rebuild keeps Kept : The plan with its vCPU, memory and disk size, the hostname, the floating IPs attached to the instance, and its [backups](/docs/cloud-compute/backups/). Erased : Every file on the disk, the instance's [snapshots](/docs/cloud-compute/snapshots/), and the SSH keys saved on the **Access** tab. The instance also gets a new random password, which we do not show. > [!WARNING] > The confirmation dialog says your snapshots stay. They do not: they go with the old disk. To keep a copy of the disk, [create a backup](/docs/cloud-compute/backups/) before you rebuild. ## Before you begin - Back up anything on the disk that you want to keep. A rebuild cannot be undone. - The instance is not suspended. - Leave [rescue mode](/docs/cloud-compute/rescue-mode/) first if the instance is in it. - Choose an image that fits the instance. Each image shows the disk it needs, such as "Needs 20 GB", and the instance's disk size is on the **Disk** card of the **Console** tab. ## Rebuild the instance 1. In the [dashboard](https://www.coritan.com/dashboard/compute), go to **Cloud Compute**, open the instance and select the **Access** tab. The **Rebuild operating system** card is at the bottom. 2. Choose an image. The images are grouped by distribution, and each one names the user you sign in as and the disk it needs. **Current** marks the image the instance runs now; choose it to reinstall the same release. 3. Select **Rebuild instance…**. 4. In the dialog, type the instance's hostname to confirm, then select **Erase and rebuild**. The **Danger zone** card on the **Settings** tab has the same rebuild: its **Open Access** button brings you to this card. ## Result We stop the instance, replace its disk with a fresh copy of the image, and start it again with the same hostname and primary address. A message confirms that the rebuild has started. When it has finished, reload the page: the header names the new image. To sign in to the rebuilt instance, do one of these: - Save your SSH keys again on the **Access** tab, then select **Restart** in the header, as [Manage SSH keys](/docs/cloud-compute/access/#manage-ssh-keys) describes. - [Reset the password](/docs/cloud-compute/access/#reset-the-password). This needs the QEMU guest agent, which not every image includes. The rebuilt instance has new SSH host keys, so your SSH client warns that the host identification has changed. [Connect to an instance over SSH](/docs/cloud-compute/connect-to-an-instance/#troubleshooting) shows how to clear the warning. ## Troubleshooting `Cannot rebuild a suspended instance` : The service behind the instance is suspended. Settle it from the **Billing** tab first. `Template not available (ready) on this node` : The image is not ready on the host that runs your instance, although it is ready elsewhere. Choose another image, or try again later. `No templates are ready` : None of the images is ready right now. Try again later. `Too many requests for this action. Please wait and try again.` : You started more than 8 rebuilds of this instance in 10 minutes. Wait for the time the response gives in `Retry-After`. A second floating IP does not answer after the rebuild : The rebuild sets up only the instance's primary address. For each other floating IP on the instance, [detach it and attach it again](/docs/floating-ips/attach-and-detach/), then restart the instance from the header. ## Related - [Operating system images](/docs/cloud-compute/images/) describes what each image sets up on its first boot. - [Back up and restore an instance](/docs/cloud-compute/backups/) to keep a copy of the disk first. - [Repair an instance in rescue mode](/docs/cloud-compute/rescue-mode/) to fix a system without erasing it. ## With the API `POST /api/v1/client/vps/{uuid}/rebuild` starts a rebuild. Send the image's `template_id`, which `GET /api/v1/client/vps/templates` lists, as [Operating system images](/docs/cloud-compute/images/#with-the-api) describes. ```bash curl -X POST https://api.coritan.com/api/v1/client/vps/$INSTANCE_UUID/rebuild \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"template_id": 7}' ``` The response is `{"status": "rebuild_queued"}`, and the rebuild runs in the background. Follow it with `GET /api/v1/client/vps/{uuid}/tasks`: the host destroys the old machine in a `qmdestroy` task and starts the new one in a `qmstart` task, and the rebuild is done when that `qmstart` reads `OK`. A suspended instance answers `403` with `Cannot rebuild a suspended instance`, and one we are moving answers `409` with `Instance is migrating`. An image that is not ready on the instance's host answers `400` with `Template not available (ready) on this node`. More than 8 rebuilds in 10 minutes answer `429`, as [Rate limits](/docs/api/rate-limits/) describes. ## API - `POST /api/v1/client/vps/{uuid}/rebuild`: Rebuild the VPS with a new OS template (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-post-api-v1-client-vps-uuid-rebuild) # Take and roll back instance snapshots > Save an instance's disk, and optionally its memory, as a snapshot you can roll back to on the same instance. Source: https://www.coritan.com/docs/cloud-compute/snapshots/ In the dashboard: - /dashboard/compute/…/snapshots: https://www.coritan.com/dashboard/compute A *snapshot* saves the state of an instance's disk at one moment, so you can return the disk to that state later. Take one before a risky change, such as an upgrade, and roll back if the change goes wrong. An instance can hold 8 snapshots at a time. A snapshot lives on the same storage as the instance's disk. It is quick to take and to roll back to, but it goes with the disk. A [rebuild](/docs/cloud-compute/rebuild/) or a cancellation deletes it, and restoring a backup leaves it unusable. For a copy that survives a rebuild, [create a backup](/docs/cloud-compute/backups/). ## Before you begin - The instance is not suspended. - To include the memory, the instance is running. ## Take a snapshot 1. In the [dashboard](https://www.coritan.com/dashboard/compute), go to **Cloud Compute**, open the instance and select the **Snapshots** tab. 2. Select **Take snapshot…**. 3. Under **Name**, type a name such as `before-upgrade`. Start with a letter, and use letters, digits, hyphens and underscores. Keep it to 37 characters or fewer: the form accepts 40, but a longer name fails when we take the snapshot. 4. Optionally, add a **Description** of up to 255 characters. 5. To save the memory as well, tick **Include memory (RAM state)**. Rolling back to the snapshot then resumes the instance exactly where it was. The snapshot is larger, and the instance pauses while we save its memory. 6. Select **Take snapshot**. The snapshot appears in the table with its **Contents**, `Disk` or `Disk and memory`, and when it was **Taken**. ## Roll back to a snapshot Rolling back returns the disk to the snapshot and loses everything written to it since. 1. On the **Snapshots** tab, open the menu at the end of the snapshot's row and select **Roll back…**. 2. Type the snapshot's name to confirm, then select **Roll back**. We shut the instance down, roll the disk back and start it again. A snapshot with memory resumes from its saved state. The snapshot stays, so you can roll back to it again later. > [!TIP] > Take a fresh snapshot before you roll back if you might need the current state. ## Delete a snapshot 1. On the **Snapshots** tab, open the menu at the end of the snapshot's row and select **Delete snapshot…**. 2. Type the snapshot's name to confirm, then select **Delete snapshot**. Deleting a snapshot frees its space on the storage. The instance itself does not change. ## Result The table lists each snapshot with its status, and the card above it counts how many of the 8 you use. A snapshot that failed does not count. ## Troubleshooting `Snapshot limit reached` : The instance holds 8 snapshots. Delete one before you take another. `Snapshot name must be alphanumeric (start with a letter), max 40 chars` : The name has a character a snapshot name cannot hold, or does not start with a letter. Use letters, digits, hyphens and underscores only. `Snapshot failed` : The host could not take the snapshot. A name longer than 37 characters is one cause. Choose a shorter name and try again. `Rollback failed` after you rolled back a snapshot with memory : The instance may have rolled back anyway. Open the **Console** tab and check its state before you try again. `Rollback failed` or `Delete snapshot failed` after a rebuild or a restore : The rebuild or the [backup restore](/docs/cloud-compute/backups/#restore-a-backup) replaced the disk the snapshot belonged to, and the snapshot can no longer be used. If a rollback stopped the instance, start it again from the header. ## Related - [Back up and restore an instance](/docs/cloud-compute/backups/) for copies kept on separate storage. - [Rebuild an instance](/docs/cloud-compute/rebuild/) explains what a rebuild erases. - [Cloud Compute limits](/docs/cloud-compute/limits/) lists the snapshot limit with the others. ## With the API `GET /api/v1/client/vps/{uuid}/snapshots` lists the instance's snapshots, newest first. Each has an `id`, `name`, `description`, `vmstate` (`1` when it holds memory), `status` and `created_at`. `status` is `creating`, `ready`, `rolling_back`, `deleting` or `failed`. `POST /api/v1/client/vps/{uuid}/snapshots` takes a snapshot. Send `name`; `description` and `vmstate` are optional, and `vmstate` is `false` when left out. The request returns when the snapshot is ready. ```bash curl -X POST https://api.coritan.com/api/v1/client/vps/$INSTANCE_UUID/snapshots \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "before-upgrade", "description": "Before the kernel upgrade", "vmstate": false}' ``` ```json {"id": 3, "name": "before-upgrade", "proxmox_snapname": "cc-before-upgrade", "status": "ready"} ``` A ninth snapshot answers `409` with `Snapshot limit of 8 reached`. A name the rules refuse answers `400`, and so does a snapshot the host could not take, with a message that starts `Snapshot failed`. `POST /api/v1/client/vps/{uuid}/snapshots/{snapshot_id}/rollback` rolls back to a snapshot whose `status` is `ready`, and answers `{"status": "rolled_back", "id": 3}` once the instance runs again. Any other snapshot answers `404` with `Snapshot not found or not ready`. `DELETE /api/v1/client/vps/{uuid}/snapshots/{snapshot_id}` deletes a snapshot and answers `{"status": "deleted", "id": 3}`. You can delete snapshots while the instance is suspended. Taking a snapshot and rolling back answer `403` with `Instance is suspended` on a suspended instance. While we move the instance to another host, taking, rolling back and deleting answer `409` with `Instance is migrating`. ## API - `GET /api/v1/client/vps/{uuid}/snapshots`: List snapshots (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-get-api-v1-client-vps-uuid-snapshots) - `POST /api/v1/client/vps/{uuid}/snapshots`: Create snapshot (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-post-api-v1-client-vps-uuid-snapshots) - `DELETE /api/v1/client/vps/{uuid}/snapshots/{snapshot_id}`: Delete snapshot (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-delete-api-v1-client-vps-uuid-snapshots-snapshot-id) - `POST /api/v1/client/vps/{uuid}/snapshots/{snapshot_id}/rollback`: Rollback snapshot (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-post-api-v1-client-vps-uuid-snapshots-snapshot-id-rollback) # Back up and restore an instance > Create full backups of an instance, restore one over its disk, and delete the backups you no longer need. Source: https://www.coritan.com/docs/cloud-compute/backups/ In the dashboard: - /dashboard/compute/…/backups: https://www.coritan.com/dashboard/compute A *backup* is a full copy of an instance's disk, saved as a compressed archive. Restore a backup to put the disk back the way it was when we made the copy. An instance can hold 16 backups. Each backup is an archive of its own, apart from the disk it copies. When you [rebuild](/docs/cloud-compute/rebuild/) the instance, its backups stay, and you can restore one over the new disk. Cancelling the instance deletes its backups. For quick restore points on the same disk, [take a snapshot](/docs/cloud-compute/snapshots/). ## Before you begin - The instance is not suspended. - No other backup, restore or backup deletion is running on the instance. They run one at a time. - The instance has not used more than its monthly traffic allowance. We make no backups of an instance that is over it until the next month starts. [How instance traffic is counted](/docs/cloud-compute/traffic/) explains the allowance. ## Create a backup 1. In the [dashboard](https://www.coritan.com/dashboard/compute), go to **Cloud Compute**, open the instance and select the **Backups** tab. 2. Select **Create backup…**. 3. Optionally, add a **Note** of up to 255 characters, such as `Before the database migration`. The table shows the note as the backup's name. 4. Under **Mode**, choose how we copy a running instance: - **Snapshot (no downtime)** copies the disk while the instance keeps running. It is the default, and it suits most workloads. - **Suspend** pauses the instance while we start the copy, then resumes it. - **Stop** shuts the instance down, starts the copy and boots the instance again. It gives the most consistent copy, with the longest pause. When the instance is stopped, every mode gives the same consistent copy. 5. Under **Compression**, keep **zstd (fast, recommended)** unless you have a reason to change it. **lzo (fastest, larger)** and **gzip (smallest, slowest)** trade speed against size, and **None** stores the copy uncompressed. 6. Select **Start backup**. The backup appears at the top of the table with the status `Creating`. You can leave the page while it runs. Reload the page to see its progress. ## Restore a backup Restoring replaces the instance's disk with the backup and loses everything written to the disk since the backup was made. 1. On the **Backups** tab, open the menu at the end of a `Completed` backup's row and select **Restore backup…**. 2. Type `restore` to confirm, then select **Restore backup**. We stop the instance and write the backup over its disk. When the disk is ready, we start the instance, even if it was stopped before. The backup shows `Restoring` until then, and stays in the table afterwards. A restore also brings back what the instance was like at the moment of the backup: its password, SSH keys, hostname, network settings and size. After the restore: - If you changed the password, SSH keys or hostname since the backup, set them again on the [Access tab](/docs/cloud-compute/access/). - If the instance's address changed since the backup, [regenerate cloud-init](/docs/cloud-compute/access/#regenerate-cloud-init) and restart the instance from the header. - If you changed the plan since the backup, [contact support](/docs/support/) so we can apply it to the instance again. - Take new snapshots. The restore replaces the disk that the instance's snapshots belong to, and rolling back to one of them fails. > [!WARNING] > Create a fresh backup or snapshot first if you might need the disk's current state. ## Delete a backup 1. On the **Backups** tab, open the menu at the end of the backup's row and select **Delete backup…**. 2. Type `delete` to confirm, then select **Delete backup**. We delete the archive from backup storage, and the backup leaves the table once it is gone. You cannot undo this. A backup that shows `Failed` stored nothing. Select **Remove backup…** on its row, then **Remove backup**, to take it off the list. ## Result The table lists each backup with its **Status**, its **Size** and when it was **Created**, with how long it took under the date. A backup without a note is named by its number, such as `Backup #12`. `Creating`, `Completed` and `Restoring` backups count toward the 16 an instance can hold. `Failed` backups do not. ## Troubleshooting **Create backup…** is greyed out : A backup or restore is already running on the instance, or the instance is suspended. Wait for the running one to finish, then reload the page. If the instance is suspended, the alert at the top of the page says why, and you can create backups again once the suspension ends. `A backup or restore is already in progress on this instance` : A backup, a restore or a deletion is running on the instance. Wait for it to finish, reload the page and try again. `Backup limit of 16 reached` : The instance holds 16 backups. Delete one you no longer need, then create the new one. `Monthly traffic quota exceeded; backups are blocked until the next period` : The instance has used more than its traffic allowance this month. You can create backups again when the next month starts. The **Bandwidth** card on the **Console** tab shows the date it resets. `Too many requests for this action. Please wait and try again.` : You started more than 8 backups, or more than 8 restores, of the instance within five minutes. Wait a few minutes and try again. `Cannot delete a backup while it is still running` : The backup is still being created or restored. Wait until it shows `Completed`, then delete it. The backup shows `Failed` : We could not finish the copy. Remove the failed backup and create a new one. If backups keep failing, [contact support](/docs/support/) with the instance's hostname. The instance does not come back after a restore : Open the **Console** tab to see what the instance is doing. If it is stopped, start it from the header. A second floating IP does not answer after a restore : The restore brings back the network settings from the moment of the backup. For each other floating IP on the instance, [detach it and attach it again](/docs/floating-ips/attach-and-detach/), then restart the instance from the header. ## Related - [Take and roll back instance snapshots](/docs/cloud-compute/snapshots/) for quick restore points on the same disk. - [Rebuild an instance](/docs/cloud-compute/rebuild/) explains what a rebuild keeps and erases. - [How instance traffic is counted](/docs/cloud-compute/traffic/) explains the monthly allowance that backups depend on. - [Cloud Compute limits](/docs/cloud-compute/limits/) lists the backup limits with the others. ## With the API `GET /api/v1/client/vps/{uuid}/backups` lists the instance's backups, newest first. Each has an `id`, a `note`, a `status`, `size_bytes`, `created_at` and `completed_at`, and `proxmox_volid`, the archive's name in backup storage. `status` is `creating`, `completed`, `failed`, `restoring` or `deleting`. `POST /api/v1/client/vps/{uuid}/backups` starts a backup. Every field is optional: - `note`: up to 255 characters. - `mode`: `snapshot` (the default), `suspend` or `stop`. - `compress`: `zstd` (the default), `lzo`, `gzip`, or `0` for none. ```bash curl -X POST https://api.coritan.com/api/v1/client/vps/$INSTANCE_UUID/backups \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"note": "Before the database migration", "mode": "snapshot", "compress": "zstd"}' ``` ```json {"backup_id": 12, "status": "creating", "mode": "snapshot"} ``` The request returns as soon as we queue the backup. List the backups again until its `status` is `completed` or `failed`. `POST /api/v1/client/vps/{uuid}/backups/{backup_id}/restore` takes no body and restores a `completed` backup. It answers `{"status": "restore_queued"}`. The backup's `status` is `restoring` until the instance runs again, then returns to `completed`. `GET /api/v1/client/vps/{uuid}/tasks` shows the host's `qmrestore` task and then its `qmstart` task. `DELETE /api/v1/client/vps/{uuid}/backups/{backup_id}` deletes a backup and answers `{"status": "delete_queued"}`. The backup shows `deleting` until we remove it from the list. These requests can answer: - `400` with `mode must be snapshot, suspend, or stop` for another `mode`. - `403` with `Monthly traffic quota exceeded; backups are blocked until the next period` when you start a backup of an instance that is over its allowance. - `404` with `Backup not found` when the instance has no backup with that `id`, or `Backup not found or not completed` when you restore a backup that is not `completed`. - `409` with `A backup or restore is already in progress on this instance`, `Backup limit of 16 reached` or `Cannot delete a backup while it is still running`. - `409` while we move the instance to another host: `Instance is migrating` for a new backup, and a message that starts `Cannot restore` or `Cannot delete a backup` for the others. - `429` for more than 8 new backups, or more than 8 restores, of one instance in five minutes, as [Rate limits](/docs/api/rate-limits/) describes. The [Cloud Compute API reference](/docs/api/reference/client/cloud-compute/#op-post-api-v1-client-vps-uuid-backups) lists every field. ## API - `GET /api/v1/client/vps/{uuid}/backups`: List backups for this instance (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-get-api-v1-client-vps-uuid-backups) - `POST /api/v1/client/vps/{uuid}/backups`: Create a new backup of this instance (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-post-api-v1-client-vps-uuid-backups) - `DELETE /api/v1/client/vps/{uuid}/backups/{backup_id}`: Delete a backup (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-delete-api-v1-client-vps-uuid-backups-backup-id) - `POST /api/v1/client/vps/{uuid}/backups/{backup_id}/restore`: Restore from a backup (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-post-api-v1-client-vps-uuid-backups-backup-id-restore) # Repair an instance in rescue mode > Boot an instance from a rescue image to repair its disk, reset a lost password or copy data off it. Source: https://www.coritan.com/docs/cloud-compute/rescue-mode/ In the dashboard: - /dashboard/compute/…/rescue: https://www.coritan.com/dashboard/compute *Rescue mode* starts an instance from a rescue image on its host instead of from its own disk. The instance's disk stays attached without being started, so you can mount it and repair it from outside. Use rescue mode when the instance no longer boots, when you cannot sign in to it, or to copy data off a disk with problems. Rescue mode changes only how the instance boots. Its disk, addresses, snapshots and backups stay as they are. ## Before you begin - The instance is not suspended. - Nothing on the instance needs to keep running. Entering rescue mode restarts the instance at once, and its services stay down until you leave rescue mode. - You work in the rescue image through the [console](/docs/cloud-compute/console/). Rescue images have no SSH sign-in of their own. ## Enter rescue mode 1. In the [dashboard](https://www.coritan.com/dashboard/compute), go to **Cloud Compute**, open the instance and select the **Rescue** tab. 2. Under **Rescue media on this host**, select the image to boot, such as SystemRescue. Each card shows the image's name, version and file. 3. Select **Enter rescue mode…**, then **Reboot into rescue**. We restart the instance from the rescue image. If the instance was stopped, we start it. The **Rescue mode** card then shows **In rescue**, and the header shows a badge with `Rescue` and the image's file name. While the instance is in rescue mode, every start and restart boots the rescue image again. Leave rescue mode to boot from the disk. ## Work on the disk These steps are for Linux instances. 1. Open the **Console** tab. SystemRescue signs you in as `root` without a password. 2. Find the instance's disk and its partitions: ```bash lsblk -f ``` The disk is usually `/dev/sda`, or `/dev/vda` on some instances. The rescue image and the instance's cloud-init drive show as CD drives, such as `sr0`, with the filesystem `iso9660`. The root filesystem is normally the largest partition. The commands below use `/dev/sda1`, so replace it with yours. 3. To check a filesystem that will not mount or that the instance reports as damaged, run the check before you mount it. Use `e2fsck` for `ext4` and `xfs_repair` for `xfs`, as `lsblk -f` shows: ```bash e2fsck -f /dev/sda1 ``` 4. Mount the root filesystem and switch into it: ```bash mount /dev/sda1 /mnt for d in dev proc sys; do mount --bind /$d /mnt/$d; done chroot /mnt ``` You now work inside the instance's own system. For example, run `passwd ubuntu` to set a new password for the `ubuntu` user. Use the user name the **Access** tab shows under **Username**. 5. When you are done, leave the chroot and unmount everything: ```bash exit umount -R /mnt ``` > [!NOTE] > On AlmaLinux, Rocky Linux, CentOS Stream and Fedora, SELinux can refuse a password you set from rescue mode. Run `touch /.autorelabel` inside the chroot after `passwd`. The next boot relabels the files, then restarts the instance on its own. ## Copy data off the instance Rescue images do not run cloud-init, so the rescue system may come up without the instance's address. Check with `ip addr`. If the interface has no public address, give it the settings the instance uses itself: 1. Mount the root filesystem at `/mnt`, as above. 2. Find the file that holds the instance's network settings by searching for its address, and read the prefix length and gateway in it: ```bash grep -rsl 203.0.113.10 /mnt/etc/ ``` 3. Set the same address and gateway by hand. Replace `ens18` with the interface name that `ip link` shows: ```bash ip link set ens18 up ip addr add 203.0.113.10/24 dev ens18 ip route add default via 203.0.113.1 dev ens18 onlink ``` Then copy the files to a machine you control, for example: ```bash rsync -a /mnt/var/www/ alex@backup.example.com:/srv/restore/ ``` ## Leave rescue mode 1. On the **Rescue** tab, select **Leave rescue mode…**. 2. Select **Reboot from disk**. We remove the rescue image, restore the boot order and restart the instance from its own disk. ## Result The **Rescue mode** card shows **Normal boot**, the badge leaves the header, and the instance runs from its own disk with the changes you made. ## Troubleshooting `No rescue images on this host` : The instance's host has no rescue image. [Contact support](/docs/support/) and ask us to add one. `Could not list the rescue media` : We could not reach the instance's host. Select **Try again**. If the error stays, [contact support](/docs/support/). `Already in rescue mode` or `Not in rescue mode` : The page is out of date. Reload it to see the instance's current mode. `Instance is suspended` : A suspended instance cannot enter or leave rescue mode. The alert at the top of the page says why the instance is suspended. You can leave rescue mode once the suspension ends. The instance boots the rescue image after a restart : The instance is still in rescue mode. Select **Leave rescue mode…** on the **Rescue** tab. You cannot sign in with the new password after you leave rescue mode : On AlmaLinux, Rocky Linux, CentOS Stream and Fedora, enter rescue mode again and run `touch /.autorelabel` in the chroot, as the note above describes. ## Related - [Use an instance's console](/docs/cloud-compute/console/) explains the display you work in. - [Manage an instance's password, SSH keys and hostname](/docs/cloud-compute/access/) resets the password without rescue mode while the instance still runs. - [Rebuild an instance](/docs/cloud-compute/rebuild/) starts again from a fresh image. Leave rescue mode first. - [Back up and restore an instance](/docs/cloud-compute/backups/) puts back a whole disk from a backup. ## With the API `GET /api/v1/client/vps/{uuid}/rescue/media` lists the ISO images on the instance's host. Each has a `volid`, its `storage`, its `size` in bytes and its `format`. ```bash curl https://api.coritan.com/api/v1/client/vps/$INSTANCE_UUID/rescue/media \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` `POST /api/v1/client/vps/{uuid}/rescue/enter` restarts the instance from the image you name in `iso_volid`, a `volid` from that list: ```bash curl -X POST https://api.coritan.com/api/v1/client/vps/$INSTANCE_UUID/rescue/enter \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"iso_volid": "local:iso/systemrescue.iso"}' ``` It returns once the instance has restarted, with `{"status": "rescue", "iso_volid": "local:iso/systemrescue.iso", "drive": "ide3"}`. A value without its storage prefix answers `400` with `iso_volid is required (e.g. local:iso/rescue.iso)`, and an instance already in rescue mode answers `409` with `Already in rescue mode`. `POST /api/v1/client/vps/{uuid}/rescue/exit` takes no body. It restarts the instance from its disk and answers `{"status": "normal"}`, or `409` with `Not in rescue mode`. `GET /api/v1/client/vps/{uuid}` shows the mode: `rescue_mode` is `1` in rescue mode and `0` otherwise, and `rescue_iso` names the image. Entering and leaving answer `403` with `Instance is suspended` on a suspended instance. Listing the media and both changes answer `409` with `Instance is migrating` while we move the instance to another host. ## API - `POST /api/v1/client/vps/{uuid}/rescue/enter`: Rescue enter (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-post-api-v1-client-vps-uuid-rescue-enter) - `POST /api/v1/client/vps/{uuid}/rescue/exit`: Rescue exit (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-post-api-v1-client-vps-uuid-rescue-exit) - `GET /api/v1/client/vps/{uuid}/rescue/media`: Rescue media (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-get-api-v1-client-vps-uuid-rescue-media) # Manage an instance's addresses and names > See an instance's IP addresses, attach floating IPs, set reverse DNS and give the instance a name in your own domain. Source: https://www.coritan.com/docs/cloud-compute/networking/ In the dashboard: - /dashboard/compute/…/network: https://www.coritan.com/dashboard/compute The **Network** tab shows the IP addresses that reach an instance, the floating IPs attached to it and the traffic it has used this month. From the tab you can also give the instance a name in one of your domains, set the reverse DNS of each address and put a web address in front of it. Each instance comes with one IPv4 address, its *primary address*. More addresses are [floating IPs](/docs/floating-ips/) that you order and attach. ## Before you begin To name the instance, you need a DNS zone on your account in Coritan DNS. That can be your own domain, as [Add a domain you already own](/docs/websites/add-an-existing-domain/) describes, or a free name under coritan.gg, as [Claim a free coritan.gg name](/docs/proxies/coritan-gg-names/) describes. ## See the instance's addresses In the [dashboard](https://www.coritan.com/dashboard/compute), go to **Cloud Compute**, open the instance and select the **Network** tab. **Addresses** : Every address attached to the instance, primary first. Each row shows the **Address** with its version and prefix length, its **Gateway**, its **Role** and **Since** when it has been attached. The primary address shows `Primary` under **Role**. Select **Copy address** to copy an address. **Floating IPs** : The floating IPs on your account that are attached to the instance, with their **Region** and **Since** date. Select an address to open it on the **Floating IPs** page, or **Reverse DNS** to open its reverse DNS. **Interfaces** : The instance's virtual network cards. Each shows its MAC address, which **Copy MAC address** copies, and its model and port speed. **Traffic** : How much the instance has sent and received this month, against its plan's allowance. [How instance traffic is counted](/docs/cloud-compute/traffic/) explains the figures. ## Attach another address 1. On the **Floating IPs** card, select **Attach floating IP**. The **Floating IPs** page opens. 2. Attach the floating IP to the instance, as [Attach and detach a floating IP](/docs/floating-ips/attach-and-detach/) describes. 3. Restart the instance from the header. The instance sets up the new address when it starts, as [Start, stop and restart an instance](/docs/cloud-compute/power/#restart-from-the-dashboard-to-apply-changes) explains. The address then appears on the **Addresses** card. If you have no floating IP to attach yet, [order one](/docs/floating-ips/order-a-floating-ip/) first. ## Name the instance The **Name this instance** card gives the instance a hostname in a zone you hold, such as `vm.example.com`. It writes an `A` record for each IPv4 address and an `AAAA` record for each IPv6 address attached to the instance. 1. On the **Network** tab, find the **Name this instance** card. 2. Under **Hostname**, type the full name, such as `vm.example.com`. If you hold a coritan.gg name, the field already holds a name under it, and selecting one of the names listed under the field fills it in. 3. Leave **Set reverse DNS to match** ticked to set the same name as the reverse DNS of the primary address. Mail servers expect it there. 4. Leave **Set the guest hostname too** unticked. The request fails with it ticked, because the instance's hostname cannot hold dots. To change the hostname inside the instance, use the **Access** tab, as [Change the hostname](/docs/cloud-compute/access/#change-the-hostname) describes. 5. Select **Name this instance**. The card confirms which records it wrote. Records the name already had stay in the zone, so remove any that point elsewhere on the zone's DNS page, as [Add, edit and delete DNS records](/docs/websites/dns/manage-dns-records/) describes. We write the new records with a TTL of 300 seconds. ## Set reverse DNS *Reverse DNS* is the name a lookup of an address returns. Mail servers compare it with the name your mail server gives when it connects, so set it on any address that sends mail. Set it to a name that resolves back to the same address. 1. On the **Reverse DNS** card, find the address and select **Edit**. 2. Type the full name, such as `mail.example.com`. 3. Select **Save**. The card shows the new name next to the address. The name must be a full domain name with at least one dot. ## Put a web address in front of the instance The **Web address** card creates a [web proxy](/docs/proxies/web-proxies/) for a site on the instance. The proxy answers at a hostname you choose and forwards the requests to port 80 of the primary address, with HTTPS and the WAF in front. 1. On the **Web address** card, type the **Hostname**, such as `www.example.com`. 2. Leave **Request an HTTPS certificate** ticked to get a certificate for the name. 3. Select the button under the form, "Give this instance a web address". When we host the name's zone, we write its DNS record for you. The card then lists the web address with **Manage route**, which opens it on the **Edge Proxy** page. [Create a web proxy](/docs/proxies/web-proxies/create-a-web-proxy/) covers the options. ## Result The **Addresses** card lists each address on the instance, the name you chose resolves to the instance's addresses, and the **Reverse DNS** card shows the name each address answers with. ## Troubleshooting `No addresses yet` : The instance has no address attached. [Attach a floating IP](/docs/floating-ips/attach-and-detach/), then restart the instance from the header. A new address does not answer : The instance sets up addresses when it starts. Restart it from the header, as the steps above describe. A `reboot` typed inside the instance does not set them up. `Enter a full hostname inside a zone you hold, such as vm.example.coritan.gg.` : Type the whole name, including the domain, such as `vm.example.com`. `Use a name inside a domain you hold here, or claim a platform name first` : The name is not inside a DNS zone on your account. [Add the domain](/docs/websites/add-an-existing-domain/) to Coritan DNS, or [claim a coritan.gg name](/docs/proxies/coritan-gg-names/), then try again. `Invalid hostname` : **Set the guest hostname too** was ticked, and nothing was saved. Untick it and select **Name this instance** again. A message that ends `is a CNAME; pick another name or remove that record first` : The name already has a `CNAME` record, which cannot sit beside an `A` record. Choose another name, or delete the `CNAME` record in the zone first. `This instance has no address yet` : Attach an address to the instance before you name it. `Could not set reverse DNS: PTR hostname must be a fully-qualified domain name` : Type a full name with a domain, such as `mail.example.com`, not a single word. A reverse lookup still returns the old name : Resolvers can keep the old answer for a few minutes. If the old name stays for longer, [contact support](/docs/support/) with the address. ## Related - [Floating IPs](/docs/floating-ips/) covers ordering, attaching and moving addresses. - [Set reverse DNS for a floating IP](/docs/floating-ips/reverse-dns/) sets reverse DNS from the **Floating IPs** page, including the hosts of a subnet. - [How instance traffic is counted](/docs/cloud-compute/traffic/) explains the **Traffic** card. - [How DNS hosting works](/docs/websites/dns/) explains the zones the name goes in. ## With the API `GET /api/v1/client/vps/{uuid}/ips` lists the addresses attached to the instance, primary first. Each has the `address`, `prefix_len`, `subnet_mask`, `gateway`, `is_primary`, `attached_at`, `role`, `status`, and the `ip_service_id` of the floating IP it belongs to. `role` is `standalone` for a single address and `host` for an address from a subnet. `GET /api/v1/client/vps/{uuid}/ptr` returns the reverse DNS of every attached address as `items`. Each item has the `address`, `is_primary` and `hostname`, which is `null` when the address has none. `PATCH /api/v1/client/vps/{uuid}/ptr` sets the reverse DNS of one address. Send `hostname`, and `address` to choose an address other than the primary one: ```bash curl -X PATCH https://api.coritan.com/api/v1/client/vps/$INSTANCE_UUID/ptr \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"hostname": "mail.example.com", "address": "203.0.113.10"}' ``` It answers with the `address` and its new `hostname`. A name that is not a full domain name answers `422` with a message that starts `PTR hostname`. An instance with no address answers `404` with `This instance has no address to name`, and an address that is not on the instance answers `404` with `That address is not attached to this instance`. `POST /api/v1/client/vps/{uuid}/name` names the instance. Send the full `hostname`, from 3 to 253 characters. `set_ptr` is `true` when left out. Leave `set_hostname` out: with a full name it answers `400` with `Invalid hostname` and saves nothing. To set the hostname inside the instance, use `PATCH /api/v1/client/vps/{uuid}/hostname`, as [Manage an instance's password, SSH keys and hostname](/docs/cloud-compute/access/#with-the-api) describes. ```bash curl -X POST https://api.coritan.com/api/v1/client/vps/$INSTANCE_UUID/name \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"hostname": "vm.example.com", "set_ptr": true}' ``` ```json { "hostname": "vm.example.com", "zone": "example.com", "records": [{"type": "A", "content": "203.0.113.10"}], "ptr": { "id": 9, "inventory_id": 41, "hostname": "vm.example.com", "address": "203.0.113.10", "version": "ipv4", "service_id": 118, "created_at": "2026-09-25T10:00:00", "updated_at": "2026-09-25T10:00:00" } } ``` `records` lists only the records the request created. `ptr` is the primary address's reverse DNS record, or `null` when you send `"set_ptr": false`. A name outside your zones answers `403`, an instance with no address answers `409` with `This instance has no address yet`, and a name that holds a `CNAME` answers `409`. The [Cloud Compute API reference](/docs/api/reference/client/cloud-compute/#op-post-api-v1-client-vps-uuid-name) lists every field. ## API - `GET /api/v1/client/vps/{uuid}/ips`: Get instance IPs (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-get-api-v1-client-vps-uuid-ips) - `GET /api/v1/client/vps/{uuid}/ptr`: Reverse DNS of every address attached to the instance (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-get-api-v1-client-vps-uuid-ptr) - `PATCH /api/v1/client/vps/{uuid}/ptr`: Set the reverse DNS of one attached address (the primary by default) (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-patch-api-v1-client-vps-uuid-ptr) - `POST /api/v1/client/vps/{uuid}/name`: Name instance (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-post-api-v1-client-vps-uuid-name) # How instance traffic is counted > How an instance's monthly traffic allowance is measured, where you see it, and what happens when you go over it. Source: https://www.coritan.com/docs/cloud-compute/traffic/ Each Cloud Compute plan includes a monthly *traffic allowance*: the amount of data an instance can transfer in a calendar month. The order page shows the allowance with each plan, in terabytes. The dashboard shows how much of it each instance has used. ## What counts - Both directions count: everything the instance receives and everything it sends. - Traffic on every network card of the instance counts, including the cards for extra [floating IPs](/docs/floating-ips/). - A terabyte here is 1024⁴ bytes. We read the instance's traffic counters every few minutes while it runs, so the figures trail the real traffic by a few minutes. A stopped instance transfers nothing, and its figures stay where they were. > [!NOTE] > The counters start again from zero each time the instance is stopped and started, including a restart from the dashboard. After that, the month's figure stays at its earlier value until the new count passes it, and the traffic from before the restart is not added to it. A run that began last month also brings the traffic from before the 1st into the new month. ## The monthly period The allowance runs by calendar month, from the 1st of one month to the 1st of the next, whatever the service's billing date. Each month starts again from zero, and unused traffic does not carry over. ## Where you see it On the instance's **Network** tab, the **Traffic** card shows the month's dates and: **Transferred** : A meter of the month's traffic against the allowance, with how many terabytes are left. A plan with no allowance shows `Unmetered on this plan`. **Inbound** and **Outbound** : The traffic received and sent this month. **Port speed** : The network speed of the plan. **Metered** : When we last read the counters. On the **Console** tab, the **Bandwidth** card shows the traffic used against the allowance, and the date it resets. ## When an instance goes over Going over the allowance does not stop the instance's traffic. The **Traffic** card shows `Over the plan allowance`, and we make no [backups](/docs/cloud-compute/backups/) of the instance until the next month starts. The backups you already have stay, and you can still restore and delete them. An instance keeps the allowance of the size you ordered. For a larger allowance, move to a new instance of a larger size, as [Get a different size](/docs/cloud-compute/billing/#get-a-different-size) describes. ## Related - [Manage an instance's addresses and names](/docs/cloud-compute/networking/) describes the rest of the **Network** tab. - [Back up and restore an instance](/docs/cloud-compute/backups/) explains the backups that pause over the allowance. - [Cloud Compute limits](/docs/cloud-compute/limits/) lists the other limits of an instance. ## With the API `GET /api/v1/client/vps/{uuid}/bandwidth` returns the instance's traffic for the month. ```bash curl https://api.coritan.com/api/v1/client/vps/$INSTANCE_UUID/bandwidth \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "period_start": "2026-09-01", "period_end": "2026-10-01", "bytes_in": 412316860416, "bytes_out": 137438953472, "bytes_used": 549755813888, "updated_at": "2026-09-25T10:05:00", "limit_tb": 2.0, "used_tb": 0.5, "remaining_tb": 1.5, "percent_used": 25.0, "over_quota": false, "bytes_limit": 2199023255552, "used": 0.5, "limit": 2.0 } ``` `period_end` is the first day of the next period. `limit_tb`, `remaining_tb`, `percent_used` and `bytes_limit` are `null` when the plan has no allowance, and `over_quota` is then always `false`. `used` and `limit` repeat `used_tb` and `limit_tb`. ## API - `GET /api/v1/client/vps/{uuid}/bandwidth`: Get current billing period bandwidth usage vs plan traffic allotment (TB) (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-get-api-v1-client-vps-uuid-bandwidth) # Read an instance's graphs > Chart an instance's processor, memory and network use over the last day, week or month. Source: https://www.coritan.com/docs/cloud-compute/graphs/ In the dashboard: - /dashboard/compute/…/graphs: https://www.coritan.com/dashboard/compute The **Graphs** tab charts how hard an instance has been working: its processor and memory use, and the traffic it has received and sent. Use it to spot a busy period, to check whether the instance needs a larger size, or to confirm a spike you noticed. ## Before you begin The graphs come from samples the instance's host records while the instance runs. Each chart point averages the samples over a stretch of time, and the stretch is longer on the longer ranges. A new instance shows its first points on the 24-hour chart before the 7-day and 30-day charts. ## Read the graphs 1. In the [dashboard](https://www.coritan.com/dashboard/compute), go to **Cloud Compute**, open the instance and select the **Graphs** tab. 2. Under **Time range**, choose **24 hours**, **7 days** or **30 days**. 3. Move the pointer over a chart to read the time and value of each point. The tab shows four charts: **CPU usage** : The share of the instance's vCPUs in use, from 0 to 100%. At 100%, every vCPU is busy. **Memory usage** : The share of the instance's memory in use, as the host sees it. It can differ from what tools inside the instance, such as `free`, report. **Network in** : The traffic the instance received, in bytes per second. **Network out** : The traffic the instance sent, in bytes per second. Each point is an average over its part of the range, so a short spike looks lower on the 7-day and 30-day charts than on the 24-hour chart. The newest point covers the last stretch that has ended, so it trails the present, and it trails further on the longer ranges. ## Result The charts show the instance's use over the range you chose. For the month's total traffic against the plan's allowance, see [How instance traffic is counted](/docs/cloud-compute/traffic/). ## Troubleshooting `No graph data yet` : The host has no samples for the instance in this range. The instance may be new, or it may have been stopped for the whole range. Start the instance, choose **24 hours**, and reload the page later. `Could not load the graphs` : We could not reach the instance's host. Select **Try again**. If the error stays, [contact support](/docs/support/). The charts show a gap : The instance was stopped, or the host recorded no samples, for that stretch of time. ## Related - [Use an instance's console](/docs/cloud-compute/console/) shows the instance's use right now. - [How instance traffic is counted](/docs/cloud-compute/traffic/) explains the monthly allowance. - [Get a different size](/docs/cloud-compute/billing/#get-a-different-size) when the instance needs more vCPU or memory. ## With the API `GET /api/v1/client/vps/{uuid}/graphs` returns the samples for one time range. Set `timeframe` to `hour` (the default), `day`, `week`, `month` or `year`. The dashboard's **24 hours**, **7 days** and **30 days** are `day`, `week` and `month`. ```bash curl "https://api.coritan.com/api/v1/client/vps/$INSTANCE_UUID/graphs?timeframe=day" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` The response is a list of points, oldest first. Each point has: `time` : The time of the sample, in Unix seconds. `cpu`, `maxcpu` : The share of the vCPUs in use, from `0` to `1`, and the number of vCPUs. `mem`, `maxmem` : The memory in use and the memory size, in bytes. `netin`, `netout` : The traffic received and sent, in bytes per second. `diskread`, `diskwrite` : The data read from and written to the disk, in bytes per second. A point from a time when the instance was stopped can leave out the values. Any other `timeframe` answers `400` with `Invalid timeframe`. ## API - `GET /api/v1/client/vps/{uuid}/graphs`: Get RRD graph data from Proxmox (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-get-api-v1-client-vps-uuid-graphs) # Change an instance's plan or cancel it > Move an instance to another billing period, see what the switch costs first, get a different size, or cancel the instance. Source: https://www.coritan.com/docs/cloud-compute/billing/ In the dashboard: - /dashboard/compute/…/billing: https://www.coritan.com/dashboard/compute An instance's **Billing** tab shows its plan and what it costs. On this tab you move the instance to another billing period, such as from monthly to quarterly, or cancel it. The size you ordered sets the instance's vCPU, memory, disk and monthly traffic allowance, and it stays with the instance. [Get a different size](#get-a-different-size) explains how to move to another one. [Change a service's plan](/docs/billing/change-plan/) and [Cancel a service](/docs/billing/cancel-a-service/) explain how plan changes and cancellations are billed for every product. This page covers what they do to an instance. ## Before you begin - The service must be active. You cannot change the plan of a suspended instance until you pay the open invoice. - A plan change shuts the instance down and starts it again, even though its size stays the same. Choose a time when a short outage does no harm. ## See your plan 1. In the [dashboard](https://www.coritan.com/dashboard/compute), go to **Cloud Compute**, open the instance and select the **Billing** tab. 2. Read the **Current plan** card. **Product** : The product you ordered. **Plan** : The size and billing period the instance is on now, such as `RX-2 (Monthly)`. **Price** : What the plan costs for each billing period. **Status** : The state of the service, such as `Active` or `Suspended`. **Next renewal** : The date of the next invoice. An hourly instance shows **Used this month** instead: what it has cost so far this month. **Set up** : When we created the instance. **Service ID** : The number to quote when you contact support. The **Activity** table under the card lists setup, renewals and plan changes, with who made each one. ## Change the billing period 1. On the **Billing** tab, select **Change plan…** on the **Current plan** card. 2. In **New plan**, choose the plan you want. The list holds the other billing periods of the instance's size, such as `RX-2 (Quarterly)`, each with its price. 3. Select **Preview**. The preview shows how many days are left in this billing period and what the switch costs today: - **Credit for unused time** is the value of the days left on your current plan. - **Charge for the new plan** is the price of the new plan for the same days. - **Due now** is the difference. - When you move to a cheaper plan, **Credit you receive** is the credit we give you. 4. Select **Apply change**. If the switch needs a payment, we take it from your credit balance first. When your balance does not cover all of it, the dashboard shows `Pay the upgrade invoice to apply the plan change`. [Pay the invoice](/docs/billing/invoices/), and the new plan applies once it is paid. When the new plan applies, we shut the instance down and start it again. An instance that was stopped starts too. ## Get a different size You cannot change the size of an existing instance. The **New plan** list offers only the billing periods of the size you ordered. To move to more or fewer vCPUs, more memory, a larger disk or a larger traffic allowance: 1. [Create an instance](/docs/cloud-compute/create-an-instance/) of the size you want. 2. Copy your data to it, for example with `rsync` over SSH: ```bash rsync -a /srv/ alex@203.0.113.20:/srv/ ``` 3. Point your DNS records at the new instance's address. Floating IPs that you ordered on their own can move to the new instance instead, as [Attach and detach a floating IP](/docs/floating-ips/attach-and-detach/) describes. 4. Cancel the old instance, as below. > [!IMPORTANT] > The IPv4 address that came with the old instance cannot move with you. We release it when the old instance ends, even if you have attached it to the new one. [Contact support](/docs/support/conversations/) if you need help with the move. ## Cancel the instance 1. On the **Billing** tab, select **Cancel service…** on the **Cancel service** card. 2. Under **When**, choose when the instance ends: - **At the end of the current term** keeps it running until the paid period ends. The dialog shows the date. - **Immediately** stops and deletes it now. We do not refund the unused time. An hourly instance has no choice. It ends at once, and billing stops at the hour. 3. Type `cancel` and select **Cancel service**. > [!WARNING] > The dialog also offers **Take a snapshot first**. It keeps nothing for an instance. The instance's snapshots are stored on its disk, and we delete its backups with it. Copy anything you need off the instance before you cancel. When the instance ends, we delete: - the instance and its disk, with every snapshot on it - every backup of the instance - the IPv4 address that came with the instance, wherever it is attached, which goes back to our pool - the instance's tags Other floating IPs you attached to the instance are detached and stay on your account. We keep billing them until you [cancel them](/docs/floating-ips/billing/). DNS records and web proxies that point at the instance also stay, so delete them once the instance has gone. ## Result After a plan change, the **Current plan** card shows the new plan and the **Activity** table lists the change. The instance runs again once it has restarted. After you cancel at the end of the term, the tab shows **Cancellation scheduled** with the date the instance ends. The instance keeps running until then. After an immediate cancellation, we stop and delete the instance. ## Troubleshooting `No other plan to switch to` in place of **Change plan…** : The instance's size has no other billing period on offer. Keep the plan, or move to another size as [Get a different size](#get-a-different-size) describes. `Could not check that the host can take the new plan. Try again in a moment.` : The check the dashboard runs before a plan change did not get an answer. Select **Apply change** again. If the message stays, [contact support](/docs/support/conversations/). `Service is not active` : The instance is suspended, or its service has ended. Pay the open invoice to lift a suspension, then change the plan. `Pay the upgrade invoice to apply the plan change` : Your credit balance did not cover the switch. [Pay the invoice](/docs/billing/invoices/). The plan applies when it is paid. The instance is stopped after a plan change : The restart did not finish. Select **Start** in the instance header. If it does not start, [contact support](/docs/support/conversations/). `Cancellation scheduled` : You asked for the instance to end at the end of its term. To keep it, [contact support](/docs/support/conversations/) before the date shown. `Suspended` : We suspended the instance, usually for an unpaid invoice. The alert gives the reason. [Pay the open invoice](/docs/billing/invoices/) to restore it. ## Related - [Change a service's plan](/docs/billing/change-plan/) - [Cancel a service](/docs/billing/cancel-a-service/) - [How hourly billing works](/docs/billing/hourly-billing/) - [Create an instance](/docs/cloud-compute/create-an-instance/) - [How instance traffic is counted](/docs/cloud-compute/traffic/) - [Cancel a floating IP](/docs/floating-ips/billing/) ## With the API A plan change goes through the services API, which accepts the instance's UUID as the service reference. Read the service to find its `product_id` and `pricing_id`, then read the product. Its `pricing` list holds one row for each billing period, and each row's `id` is a plan you can move to. ```bash curl -s https://api.coritan.com/api/v1/services/$INSTANCE_UUID \ -H "Authorization: Bearer $CORITAN_TOKEN" curl -s https://api.coritan.com/api/v1/products/$PRODUCT_ID \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` Preview the cost, then change the plan: ```bash curl -s "https://api.coritan.com/api/v1/services/$INSTANCE_UUID/upgrade-preview?new_pricing_id=42" \ -H "Authorization: Bearer $CORITAN_TOKEN" curl -s -X POST https://api.coritan.com/api/v1/services/$INSTANCE_UUID/change-plan \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"new_pricing_id": 42}' ``` The response's `status` is `applied` when the new plan applies now. It is `payment_required` when the switch waits for its invoice, whose number is in `invoice_id`. A plan from another product answers `400` with `New pricing must belong to the same product` or `Plan changes must stay within the same hardware tier`. The change also answers `400` with `Service is not active` or `Already on this plan`, and `404` with `Pricing tier not found`. Before it changes the plan, the dashboard sends the new plan's vCPU, memory and disk to `resize-preview`. A field you leave out keeps its current value. ```bash curl -s -X POST https://api.coritan.com/api/v1/client/vps/$INSTANCE_UUID/resize-preview \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"cpu_cores": 2, "memory_mb": 2048, "disk_gb": 40}' ``` ```json { "current": {"cpu_cores": 2, "memory_mb": 2048, "disk_gb": 40}, "requested": {"cpu_cores": 2, "memory_mb": 2048, "disk_gb": 40}, "hotplug_eligible": true, "requires_stop": false } ``` `hotplug_eligible` is `true` when the disk stays the same, vCPU and memory do not shrink, and the instance is running. `requires_stop` is its opposite. A plan change restarts the instance whatever they say. A `disk_gb` smaller than the instance's disk answers `400` with `Disk can only grow`. To cancel, send `immediate` as `true` to end the instance now, or `false` (the default) to end it at the end of its term. `reason` is optional. `keep_snapshot` keeps nothing for an instance, and the response's `snapshot_taken` is always `false`. ```bash curl -s -X POST https://api.coritan.com/api/v1/services/$INSTANCE_UUID/cancel \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"immediate": false, "reason": "Moved to a larger instance"}' ``` The response gives the new `status` and the `termination_date`. A second request answers `400` with `Service already scheduled for end-of-term termination` or `Service already cancelled/terminated`. The [Cloud Compute API reference](/docs/api/reference/client/cloud-compute/#op-post-api-v1-client-vps-uuid-resize-preview) and the [services API reference](/docs/api/reference/client/services/#op-post-api-v1-services-service-ref-change-plan) list every field. ## API - `POST /api/v1/client/vps/{uuid}/resize-preview`: Capacity/mode preview for a plan change resize (billing via /services change-plan) (https://www.coritan.com/docs/api/reference/client/cloud-compute/#op-post-api-v1-client-vps-uuid-resize-preview) # Rename and tag an instance > Change an instance's hostname, add tags, and copy the identifiers support and the API ask for. Source: https://www.coritan.com/docs/cloud-compute/settings/ In the dashboard: - /dashboard/compute/…/settings: https://www.coritan.com/dashboard/compute The **Settings** tab holds an instance's name, its tags and the identifiers that support and the API ask for. It also links to the tabs where you rebuild or cancel the instance. ## Before you begin - A new name reaches the inside of the instance when you next start or restart it from the dashboard. To apply it at once, the instance must be running so that it can restart. - The instance must not be suspended or moving to another host. Renaming a suspended instance fails with `Instance is suspended`. ## Rename the instance 1. In the [dashboard](https://www.coritan.com/dashboard/compute), go to **Cloud Compute**, open the instance and select the **Settings** tab. 2. On the **Name** card, type the new name under **Hostname**. Use letters, digits and hyphens, up to 63 characters, with no hyphen at the start or end. 3. To apply it now, tick **Reboot now so the guest picks it up**. Leave it clear to apply the name at the next start. 4. Select **Save hostname**. The **Cloud Compute** list and the instance header show the new name at once. Inside the instance, cloud-init sets it as the hostname at the next start or restart from the dashboard. The **Hostname** card on the **Access** tab changes the same name, as [Change the hostname](/docs/cloud-compute/access/#change-the-hostname) describes. A hostname is one label, such as `web-2`, and it creates no DNS record. To reach the instance at a name such as `web-2.example.com`, follow [Manage an instance's addresses and names](/docs/cloud-compute/networking/). ## Tag the instance 1. On the **Tags** card, type a tag in **Add a tag**, such as `production`. 2. Press Enter or select **Add tag**. **Suggestions** offers tags you already use on other instances. 3. To remove a tag, select the cross on its chip. A tag uses lowercase letters, digits and hyphens, and starts with a letter or a digit. The dashboard saves each change at once. Tags on the instance filter the **Cloud Compute** list. Its service keeps a separate set, as [Tag your services](/docs/get-started/tags/) explains. ## Find the instance's identifiers The **Identifiers** card lists what support asks for when you contact them. **Instance** : The instance's ID, a UUID. The card shows its first eight characters. Select **Copy instance ID** to copy all of it. The API uses this ID in every Cloud Compute path. **VM ID** : The number of the virtual machine on its host. **Node** : The host the instance runs on, with its location. **Service** : The service that carries the instance's plan and billing, as `#` and its number. Select it to open the service. **Created** : When we created the instance. **State** : Whether the instance is running or stopped now. ## Rebuild or cancel the instance The **Danger zone** card links to the two actions that erase the instance's data. Each one asks you to type a word before it goes ahead. - **Rebuild operating system** wipes the disk and installs a fresh image. Select **Open Access** to go to the **Access** tab, then follow [Rebuild an instance](/docs/cloud-compute/rebuild/). - **Cancel service** stops billing and deletes the instance. Select **Open Billing** to go to the **Billing** tab, then follow [Cancel the instance](/docs/cloud-compute/billing/#cancel-the-instance). ## Result The instance has its new name in the dashboard at once. Inside the instance, the name applies after the next start or restart from the dashboard. The instance's tags show on the **Tags** card and in the **Cloud Compute** list. ## Troubleshooting `Letters, digits and dashes only; up to 63 characters; no leading or trailing dash.` : The name has a dot, a space or another character a hostname cannot hold, or it is too long. Use one label, such as `web-2`. **Save hostname** stays greyed out : The name is the one the instance already has, or it breaks the rules above. Change it, or correct the mistake the field shows. **Reboot now so the guest picks it up** is greyed out : The instance is stopped. Save the name without it. The instance takes the name when you start it. `hostname` inside the instance still prints the old name : The instance has not booted since you saved the name. Select **Restart** in the instance header. A `reboot` typed inside the instance does not apply the name. `Instance is suspended` : We suspended the instance, usually for an unpaid invoice. Select **Open Billing** in the alert at the top of the page and pay the open invoice. **Could not save the tags** with `Tag must be 1–128 chars: lowercase letters, digits, hyphen, underscore` : The tag has a character that tags cannot hold, such as a dot, a colon or a slash, or it starts with a hyphen. Use lowercase letters, digits and hyphens. ## Related - [Manage an instance's password, SSH keys and hostname](/docs/cloud-compute/access/) - [Tag your services](/docs/get-started/tags/) - [Rebuild an instance](/docs/cloud-compute/rebuild/) - [Change an instance's plan or cancel it](/docs/cloud-compute/billing/) - [Contact support from the dashboard](/docs/support/conversations/) ## With the API `GET /api/v1/client/vps/{uuid}` returns the identifiers on this tab: `uuid`, `vmid`, `node_name`, `location_name`, `service_id`, `created_at` and `status`. ```bash curl -s https://api.coritan.com/api/v1/client/vps/$INSTANCE_UUID \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` To rename the instance, send `hostname`. Add `"reboot": true` to restart it at once. ```bash curl -s -X PATCH https://api.coritan.com/api/v1/client/vps/$INSTANCE_UUID/hostname \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"hostname": "web-2", "reboot": true}' ``` ```json {"hostname": "web-2", "rebooted": true} ``` `rebooted` repeats what you asked for. A stopped instance does not start, even when it says `true`. A name that breaks the rules answers `400` with `Invalid hostname`, or `422` when it is longer than 63 characters. An instance's tags use the source type `vps` and the instance's UUID: ```bash curl -s -X PUT https://api.coritan.com/api/v1/client/tags/sources/vps/$INSTANCE_UUID \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"tags": ["production", "eu-customers"]}' ``` [Tag your services](/docs/get-started/tags/#with-the-api) covers the rest of the tags API. The [Cloud Compute API reference](/docs/api/reference/client/cloud-compute/#op-patch-api-v1-client-vps-uuid-hostname) lists every field. # Cloud Compute limits > The limits on instance sizes, snapshots, backups, names and API calls, in one place. Source: https://www.coritan.com/docs/cloud-compute/limits/ This page lists every fixed limit on a Cloud Compute instance. The plan sets the rest, and the page that covers each feature explains how to work within it. ## Size The size you order sets each instance's vCPU, memory, disk and monthly traffic allowance. The **Billing** tab shows the plan, and the order page shows what each size includes. | What | Limit | | --- | --- | | Changing size | Not possible on an existing instance. Move to a new instance of the size you want. | | Changing plan | Only to another billing period of the same size. | | Traffic allowance | Counted per calendar month, in both directions. One TB is 1024⁴ bytes. | [Change an instance's plan or cancel it](/docs/cloud-compute/billing/) covers plan changes and moving to another size, and [How instance traffic is counted](/docs/cloud-compute/traffic/) covers the allowance. ## Snapshots and backups | What | Limit | | --- | --- | | Snapshots per instance | 8. A snapshot that failed does not count. | | Snapshot name | Starts with a letter, then letters, digits, hyphens and underscores. Keep it to 37 characters: the form and the API accept 40, but a longer name fails when we take the snapshot. | | Snapshot description | 255 characters in the dashboard. | | Backups per instance | 16. | | Backup note | 255 characters. | | Backup jobs at once | One per instance: a backup, a restore or a deletion. | | New backups once the traffic allowance is used up | Blocked until the next month. Restores and deletions still work. | [Take and roll back instance snapshots](/docs/cloud-compute/snapshots/) and [Back up and restore an instance](/docs/cloud-compute/backups/) explain each one. ## Names | What | Limit | | --- | --- | | Hostname when you order | Letters, digits, hyphens and dots, up to 100 characters. Each part between dots is at most 63 characters. | | Hostname on the **Access** and **Settings** tabs | One label of letters, digits and hyphens, up to 63 characters, with no hyphen at the start or end. | | Name on the **Network** tab | A full name in a zone you hold, from 3 to 253 characters. | | Reverse DNS name | A full name, up to 253 characters. | ## Console | What | Limit | | --- | --- | | Time to connect | 120 seconds from the start of a session. The dashboard asks for a new session each time it connects. | | Consoles open at once | Limited per account. A console over the limit does not connect, and its socket closes with code `4029`. Close a console you no longer use and try again. | ## Rate limits We count these actions per account and per instance. Each limit covers a rolling window. | Action | Most per window | Window | | --- | --- | --- | | Power actions: start, stop, shut down and restart | 24 | 60 seconds | | New backups | 8 | 5 minutes | | Backup restores | 8 | 5 minutes | | Rebuilds | 8 | 10 minutes | A request over the limit fails, and the dashboard shows `Too many requests for this action. Please wait and try again.` Through the API, the request answers `429`: ```json { "detail": { "error": "rate_limited", "message": "Too many requests for this action. Please wait and try again.", "action": "vps.power", "retry_after_seconds": 60 } } ``` `action` is `vps.power`, `vps.backup`, `vps.backup_restore` or `vps.rebuild`. `retry_after_seconds` and the `Retry-After` header give the length of the window. A request refused this way does not count towards the limit. Every API request also counts towards the limits for your account as a whole, which [Rate limits](/docs/api/rate-limits/) describes. ## Queries | What | Limit | | --- | --- | | Graph time frame | `hour`, `day`, `week`, `month` or `year`. | | Host tasks per request | 50 by default, 100 at most. | ## Related - [Cloud Compute](/docs/cloud-compute/) - [Rate limits](/docs/api/rate-limits/) - [Troubleshoot instances](/docs/cloud-compute/troubleshooting/) # Troubleshoot instances > Fixes for instances that will not start, cannot be reached, refuse your password or fail a snapshot or backup. Source: https://www.coritan.com/docs/cloud-compute/troubleshooting/ Find the message or the symptom you see, then follow the fix. Each entry links to the page that covers the task in full. ## Ordering and setup An instance you ordered is not on the **Cloud Compute** page : The page lists an instance once we have built it. Open **Services** and find the order. `Pending` means it waits for payment or for setup to start, `Provisioning` means we are building it, and `Failed` means setup did not finish. [Manage your services](/docs/get-started/services/) explains each status. For a failed order, [contact support](/docs/support/conversations/) with its service number. **No address** on the instance : The instance has no public IP address. If you ordered the included address, we could not attach it, and it is on your **Floating IPs** page: [attach it](/docs/floating-ips/attach-and-detach/) to the instance, then restart the instance from the header. You do not know the instance's password : We set a password for the image's default user when we build the instance, but we never show it. Sign in with the SSH key you added, or [reset the password](/docs/cloud-compute/access/#reset-the-password) on the **Access** tab. ## Power and boot `This instance is suspended` : We suspended the service behind the instance, usually for an unpaid invoice. Select **Open Billing** in the alert and pay the open invoice. [Failed payments and suspended services](/docs/billing/failed-payments/) explains suspension. `Cannot perform power actions on a suspended instance` : The same cause as above. Pay the open invoice, then try again. `Instance is migrating` : We are moving the instance to another host. Wait until the move finishes and try again. **Stop** or **Restart** does nothing : The operating system ignored the request to shut down. **Stop** powers the instance off after 60 seconds anyway. If a restart hangs, select **Kill…** in the header's **Instance actions** menu, then **Start**. [Start, stop and restart an instance](/docs/cloud-compute/power/) covers each action. The instance boots into rescue media every time : The instance is still in rescue mode, and the header shows a `Rescue` badge. [Leave rescue mode](/docs/cloud-compute/rescue-mode/#leave-rescue-mode) on the **Rescue** tab. The console shows a boot error, or the instance never reaches a sign-in prompt : The operating system on the disk cannot start. Choose one of these fixes: - [Repair it in rescue mode](/docs/cloud-compute/rescue-mode/#work-on-the-disk) to keep the data. - [Roll back to a snapshot](/docs/cloud-compute/snapshots/#roll-back-to-a-snapshot) taken before the problem. - [Restore a backup](/docs/cloud-compute/backups/#restore-a-backup). - [Rebuild the instance](/docs/cloud-compute/rebuild/), which erases the disk. `The instance is powered off` : The console needs a running instance. Select **Start** in the header. `Could not connect to the console` : The console session expired before it connected, or the host did not answer. Select **Try again**. [Use an instance's console](/docs/cloud-compute/console/#troubleshooting) lists the other console messages. ## Connecting `Connection timed out` or `No route to host` : The instance is stopped, has no public address, or has not picked up an address you attached. Start or restart it from the dashboard header, which boots it with its current network settings. If the address is a floating IP, check the rules on its [**Shield** tab](/docs/floating-ips/shield-settings/). `Connection refused` : The address answers, but no SSH server listens on port 22. Sign in on the [display console](/docs/cloud-compute/console/) and start the SSH service, or check which port it uses. `Permission denied (publickey)` : The instance does not hold the key you used, or you signed in as the wrong user. The **Access** tab shows the user. Keys you save there apply only after you restart the instance from the header, and a rebuild does not put them back. [Connect to an instance over SSH](/docs/cloud-compute/connect-to-an-instance/#troubleshooting) has the details. The instance refuses your password : The password inside the instance is not the one you have. [Reset it](/docs/cloud-compute/access/#reset-the-password) on the **Access** tab while the instance runs. If the reset fails, set a new password from [rescue mode](/docs/cloud-compute/rescue-mode/#work-on-the-disk). `WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!` : The instance has new SSH host keys. A rebuild creates new ones, and cloud-init usually does too on the first boot after you change the instance's keys, password, hostname or network settings. If you made one of those changes, run `ssh-keygen -R` with the instance's address, such as `ssh-keygen -R 203.0.113.10`, and connect again. A second floating IP does not answer : After a rebuild or a cloud-init regeneration, the instance sets up only its primary address. A restore brings back the addresses the instance had when we made the backup. In each case, [detach the other address and attach it again](/docs/floating-ips/attach-and-detach/), then restart the instance from the header. ## Snapshots and backups `Snapshot limit reached` : The instance holds 8 snapshots. Delete one before you take another. A snapshot fails as soon as you take it : A name longer than 37 characters is one cause. Choose a shorter name. [Take and roll back instance snapshots](/docs/cloud-compute/snapshots/#troubleshooting) lists the other causes. `Backup limit of 16 reached` : Delete a backup you no longer need, then create the new one. `A backup or restore is already in progress on this instance` : The instance runs one backup job at a time. Wait until the table shows the job has finished. `Monthly traffic quota exceeded; backups are blocked until the next period` : The instance has used its traffic allowance for the month. New backups start again on the 1st. [How instance traffic is counted](/docs/cloud-compute/traffic/) explains the allowance. `Too many requests for this action. Please wait and try again.` : You sent the same action too often for this instance. [Cloud Compute limits](/docs/cloud-compute/limits/#rate-limits) lists each limit and its window. ## Size and billing The instance needs more vCPU, memory, disk or traffic : An instance keeps the size you ordered. [Get a different size](/docs/cloud-compute/billing/#get-a-different-size) explains how to move to a new instance. `Over the plan allowance` : The instance has used more traffic this month than its plan includes. Its traffic still flows, but we make no new backups until the next month. ## Errors from the API `401` : The access token is missing or has expired. [Authentication](/docs/api/authentication/) explains how to get a new one. `404` with `Cloud Compute instance not found` : The UUID is wrong, the instance belongs to another account, or it has ended. `403` with `Instance is suspended` : The service is suspended. Some actions answer `Cannot perform power actions on a suspended instance` or `Cannot rebuild a suspended instance` instead. Pay the open invoice first. `409` with `Instance is migrating` : We are moving the instance to another host. Try again once the move has finished. `429` : The action is over its limit for this instance. Wait for the number of seconds in `Retry-After`. [Cloud Compute limits](/docs/cloud-compute/limits/#rate-limits) shows the response. [Errors](/docs/api/errors/) explains the shape of every error response. ## Get help If none of these fixes the problem, [contact support from the dashboard](/docs/support/conversations/). Include: - the instance ID from the **Identifiers** card on the **Settings** tab, and the **Service ID** from the **Billing** tab - what you did and when - the message you saw, word for word ## Related - [Cloud Compute](/docs/cloud-compute/) - [Cloud Compute limits](/docs/cloud-compute/limits/) - [Troubleshoot floating IPs](/docs/floating-ips/troubleshooting/) # Apps > Run a web app from a git repository or a container image, as replicas in the regions you choose, behind your own domains. Source: https://www.coritan.com/docs/apps/ In the dashboard: - /dashboard/apps: https://www.coritan.com/dashboard/apps Apps runs a web service from its source. Point an app at a git repository and we build an image from the commit you deploy, or give it a container image that is already built. Each deployment runs as *replicas*, identical copies of the app, in the regions you choose, and visitors reach them through the app's domains. Use it for a website, an API or any other service that answers HTTP and keeps its data somewhere else, such as a database or [Object Storage](/docs/object-storage/). A replica keeps nothing on its disk from one deployment to the next. ## What an app includes - A *source*: a git repository and branch, or a container image. You choose it when you create the app, and it cannot change later. - For a git app, settings for the build: the directory in the repository, a Dockerfile or the commands to install, build and start. - A *size*, which sets the memory, CPU and disk of each replica. - The regions it runs in, and how many replicas run in each. - Environment variables, each for the build, the running app or both. - A *platform name*, `.`, and up to 20 custom domains, each with a certificate. - For a git app, a push webhook that deploys each push to the app's branch. - A history of deployments. You can roll back to any of them that went live. [How Apps works](/docs/apps/how-apps-work/) explains how these fit together. ## What you can do Set up an app: - [Create an app](/docs/apps/create-an-app/) from a repository or an image - [Set environment variables](/docs/apps/environment-variables/) for the build and the running app - [Add a custom domain](/docs/apps/custom-domains/) and verify it with a TXT record - [Deploy on every push](/docs/apps/push-webhook/) with the push webhook Ship and run it: - [Deploy an app](/docs/apps/deploy-an-app/): a branch, a commit or an image, a redeploy, or cancelling a deployment - [Read a deployment and its build log](/docs/apps/read-a-deployment/) - [Roll back to an earlier deployment](/docs/apps/roll-back-a-deployment/) - [Change or delete an app](/docs/apps/app-settings/) Look things up and fix problems: - [App and deployment fields](/docs/apps/fields/) - [Apps limits](/docs/apps/limits/) - [Troubleshoot Apps](/docs/apps/troubleshooting/) ## Find your apps in the dashboard Select **Apps** under **Products** in the sidebar to open [the list of your apps](https://www.coritan.com/dashboard/apps). Its header shows how many apps you have out of your limit, such as `3 of 20 apps`, and **New app…** creates one ([Create an app](/docs/apps/create-an-app/)). Each row shows: **App** : The app's name, and its platform address or `No platform address yet`. **Status** : **Live** while a deployment serves the app, with how many of its replicas are healthy, such as `3 of 4 replicas healthy`. **Not live** when nothing serves it, which includes an app whose first build is on its way. **Suspended** when we have suspended it. **Regions** : Its regions, or `Every region`, and how many replicas run in each. **Last deployment** : The deployment that serves the app: its commit message, or what started it, with its short commit and when it was created. `Nothing live yet` when no deployment serves it. The search box finds apps by name, name on the platform, address, repository or image. The filters **All**, **Live**, **Not live** and **Suspended** show how many apps each one holds. The button at the end of a live app's row opens its address in a new tab. Select a row to open the app. Its header shows the app's state, its address with a copy button, its regions and replicas, and where its code comes from, such as `example/web-shop on main`. On this page the state can also be a deployment on its way, such as **Building**, or **Failed** when nothing is live and the newest deployment failed, or **Not deployed**. **Deploy…** and **Redeploy…** start a deployment ([Deploy an app](/docs/apps/deploy-an-app/)), and the menu beside them has **Open app**, **Copy address** and **Copy app ID**. Four tabs hold the rest: **Deployments** : The app's deployments, newest first, and each one's own page ([Read a deployment and its build log](/docs/apps/read-a-deployment/)). **Domains** : The platform address and the custom domains ([Add a custom domain](/docs/apps/custom-domains/)). **Environment** : The environment variables ([Set environment variables](/docs/apps/environment-variables/)). **Settings** : The app's settings, its push webhook, and deleting it ([Change or delete an app](/docs/apps/app-settings/)). ## How Apps is billed An app is not a service on your account. It has no plan, it does not appear on an invoice, and it does not use your credit. [Billing](/docs/billing/) covers the services that are billed. ## Limits and availability - Apps can be turned off for the whole platform. While it is off, the sidebar has no **Apps**, the Apps pages show `Page not found`, and every Apps request answers `404` with `{"detail": "Not Found"}`, whoever sends it. - Visitors reach an app only through its domains. A replica has no public address of its own. - Each region runs the number of replicas you set. The number does not grow or shrink with traffic. - A new deployment starts on new replicas, so files an app writes to its disk do not carry over. - You can read a deployment's build log. What the app writes while it runs is not shown. - Your account and each organization can have a set number of apps. The **Apps** list shows the limit and how many you have, and so does `GET /api/v1/client/apps/regions`. [Apps limits](/docs/apps/limits/) lists every limit. ## Apps for an organization An organization can own apps too. Its apps use the same requests under `/api/v1/orgs/{org_slug}/apps`, with the same bodies, answers and errors as your own apps under `/api/v1/client/apps`. Any member of the organization can read them. Only an owner or an admin can create, change, deploy or delete one, and any other member gets `403` with `Admin access required`. [Organization roles and permissions](/docs/organizations/roles-and-permissions/) describes the roles. Deleting an organization's app also needs the app's name typed to confirm it. From a staff console session, it needs a step-up in the last 10 minutes as well ([Confirm it is you](/docs/organizations/staff-console/sign-in/#confirm-it-is-you)). Each change a member makes to an organization's app goes into [the organization's audit log](/docs/organizations/audit-log/), which names the member. The log leaves out deployments that a push starts. In the dashboard, an organization's apps are on its **Apps** tab: [Run apps for an organization](/docs/apps/organization-apps/). ## With the API Every Apps request takes your access token as `Authorization: Bearer $CORITAN_TOKEN` ([Authentication](/docs/api/authentication/)). Your own apps are under `/api/v1/client/apps`. An organization's apps are under `/api/v1/orgs/{org_slug}/apps`, and take the token of one of its members. An app that belongs to someone else answers `404` with `App not found`, as an app that does not exist does. ### List apps [`GET /api/v1/client/apps`](/docs/api/reference/client/apps/#op-get-api-v1-client-apps) lists your apps, newest first, each with its current deployment and replica counts: ```bash curl "https://api.coritan.com/api/v1/client/apps?status=active&limit=20" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "apps": [ { "uuid": "3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21", "name": "Web shop", "slug": "web-shop", "status": "active", "owner": {"type": "user"}, "source_type": "git", "repo_url": "https://github.com/example/web-shop", "repo_branch": "main", "repo_subdir": null, "repo_token_set": true, "repo_token_hint": "x9Qa", "image_ref": null, "framework": null, "install_command": null, "build_command": null, "start_command": null, "dockerfile_path": null, "port": 3000, "health_check_path": "/healthz", "instance_size": "small", "memory_mb": 512, "cpu_percent": 100, "disk_mb": 2048, "regions": ["fra", "iad"], "min_replicas": 2, "max_replicas": 2, "url": "https://web-shop.apps.example.net", "platform_hostname": "web-shop.apps.example.net", "domains_count": 2, "current_deployment": { "uuid": "8c4e1a7d-2f9b-4d6e-b3a8-6e1f9c2d5a10", "status": "ready", "source": "git", "trigger": "push", "git_ref": "main", "git_sha": "9f2c1e7b4a6d8e0f1a2b3c4d5e6f708192a3b4c5", "is_current": true }, "replicas": {"healthy": 4, "unhealthy": 0, "starting": 0, "total": 4}, "webhook": { "url": "https://api.coritan.com/api/v1/apps/hooks/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21", "secret_set": true, "secret_hint": "f0a9" }, "created_at": "2026-09-20T09:12:00Z", "updated_at": "2026-09-26T10:05:00Z" } ], "total": 1, "limit": 20, "offset": 0 } ``` `status` is `active` or `suspended`, and leaving it out lists both. `limit` takes 1–100 (50 when left out), and `offset` skips that many apps. `total` counts every app the filter matches. The example shortens `current_deployment`, which carries every field of a deployment. [App and deployment fields](/docs/apps/fields/) describes each field. The examples on these pages use `apps.example.net` for the platform's app domain. `base_domain` in the regions answer below gives the real one. ### Get one app [`GET /api/v1/client/apps/{app_uuid}`](/docs/api/reference/client/apps/#op-get-api-v1-client-apps-app-uuid) returns the app with more beside it: `app` : The app, as in the list. `domains` : Its platform name first, then its custom domains ([Add a custom domain](/docs/apps/custom-domains/)). `deployments` and `deployments_total` : The five newest deployments, and how many the app has in all ([Deploy an app](/docs/apps/deploy-an-app/#list-deployments)). `replicas` : The replicas of the deployment that serves the app, without the ones that failed ([Read a deployment](/docs/apps/read-a-deployment/#replicas)). ```bash curl https://api.coritan.com/api/v1/client/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21 \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ### Regions, sizes and limits [`GET /api/v1/client/apps/regions`](/docs/api/reference/client/apps/#op-get-api-v1-client-apps-regions) says what you can choose when you create an app: ```bash curl https://api.coritan.com/api/v1/client/apps/regions \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "regions": [ {"code": "fra", "name": "Frankfurt", "country_code": "DE"}, {"code": "iad", "name": "Ashburn", "country_code": "US"} ], "sizes": [ {"name": "small", "memory_mb": 512, "cpu_percent": 100, "disk_mb": 2048}, {"name": "medium", "memory_mb": 1024, "cpu_percent": 200, "disk_mb": 4096}, {"name": "large", "memory_mb": 2048, "cpu_percent": 400, "disk_mb": 8192} ], "base_domain": "apps.example.net", "limits": { "apps": 20, "apps_used": 3, "min_replicas": 5, "max_replicas": 10, "regions": 20, "custom_domains": 20, "env_vars": 200 } } ``` `regions` lists the regions that can run apps now. `base_domain` is the domain every platform name ends in, or `null` when the platform gives apps no platform name. In `limits`, `apps` is how many apps you may have, where `0` means no limit, and `apps_used` is how many you have. `min_replicas` and `max_replicas` are the most you can set for each, and the rest are the most regions, custom domains and variables one app can have. The same three requests under `/api/v1/orgs/{org_slug}/apps` read an organization's apps, and there `limits.apps_used` counts the organization's apps. The [organization apps reference](/docs/api/reference/organizations/organization-apps/#op-get-api-v1-orgs-org-slug-apps) lists them. ## API - `GET /api/v1/client/apps`: List apps (https://www.coritan.com/docs/api/reference/client/apps/#op-get-api-v1-client-apps) - `GET /api/v1/client/apps/regions`: Regions (https://www.coritan.com/docs/api/reference/client/apps/#op-get-api-v1-client-apps-regions) - `GET /api/v1/client/apps/{app_uuid}`: Get one app with its domains, recent deployments and current replicas (https://www.coritan.com/docs/api/reference/client/apps/#op-get-api-v1-client-apps-app-uuid) - `GET /api/v1/orgs/{org_slug}/apps`: List apps (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-get-api-v1-orgs-org-slug-apps) - `GET /api/v1/orgs/{org_slug}/apps/regions`: Regions (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-get-api-v1-orgs-org-slug-apps-regions) - `GET /api/v1/orgs/{org_slug}/apps/{app_uuid}`: Get one app with its domains, recent deployments and current replicas (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-get-api-v1-orgs-org-slug-apps-app-uuid) # How Apps works > How an app turns a commit or an image into healthy replicas in your regions, switches visitors to them and keeps them running. Source: https://www.coritan.com/docs/apps/how-apps-work/ An app is a web service we run from its source. Each version you run is a *deployment*: one image, started as replicas in the app's regions. A new deployment goes live only once its replicas pass their health check, and the one before it keeps serving until then. ## Sources An app has one of two sources, chosen when you create it: Git : A repository on a public host, reached over HTTPS, and a branch (`main` unless you choose another). Each deployment builds one commit into an image. For a private repository, save an access token with the app. An app in a monorepo can name its directory inside the repository. Image : A container image reference, such as `ghcr.io/example/web:1.4.0`. Each deployment runs that image as it is, with no build. The image must be one that anyone can pull, because an app holds no registry credentials. The source type cannot change after you create the app. To move an app from an image to git, or back, create a new app. ## Builds A git deployment starts with a build. The build fetches the one commit, without its history, from the app's directory. It does not fetch submodules, and files kept in Git LFS arrive as pointer files. The build then uses one of two methods: Dockerfile : Used when you set a Dockerfile path, or when the app's directory holds a file named `Dockerfile`. A path is looked for in the app's directory first, then at the root of the repository. The Dockerfile defines the whole build, so the install, build and start commands are not used. Railpack : Used when there is no Dockerfile. [Railpack](https://railpack.com) reads the files in the app's directory and works out how to install, build and start it. The install, build and start commands you set replace the ones it would choose. Environment variables marked for the build reach it as follows. With Railpack, the build sees each one as an environment variable. With a Dockerfile, a variable that is not secret is a build argument, which the Dockerfile reads after declaring it with `ARG NAME`, and a secret one is a build secret with the variable's name as its id, as in `RUN --mount=type=secret,id=NAME`. A plain variable whose name starts with `BUILDKIT_` is left out of a Dockerfile build. [Set environment variables](/docs/apps/environment-variables/) covers the rest. The build log hides the repository's access token and every secret build value of six characters or more, which appear as `[redacted]`. A build that runs for more than an hour is stopped and fails. ## Deployments A deployment starts when you create an app (unless you ask it not to), when you deploy, redeploy or roll back, and when the push webhook receives a push to the app's branch. It then moves through these statuses, which the dashboard shows with a capital, as in **Ready**, and spells `canceled` as **Cancelled**: | Status | Meaning | | --- | --- | | `queued` | Waiting for a build to start. Only a git deployment is queued. | | `building` | The image is being built. | | `deploying` | Replicas are starting in each region. An image deployment, a redeploy and a rollback start here. | | `ready` | Every region has its healthy replicas, and the deployment serves the app. | | `failed` | The build failed, or a region could not bring up its replicas. The deployment's page, and its `error` field, say why. | | `canceled` | You cancelled it, or it stopped because a newer deployment went live or the app was suspended or deleted. | | `superseded` | It was ready and served the app until a newer deployment went live. | A deployment that fails or is cancelled leaves the current one serving. When a deployment goes live, any older deployment that is still on its way is cancelled. [Deploy an app](/docs/apps/deploy-an-app/) shows each way to start one. ## Replicas and regions Each replica runs with the memory, CPU and disk of the app's size: | Size | Memory | CPU | Disk | | --- | --- | --- | --- | | `small` | 512 MB | 1 vCPU | 2 GB | | `medium` | 1 GB | 2 vCPU | 4 GB | | `large` | 2 GB | 4 vCPU | 8 GB | An app runs in every region that can run apps, written `["*"]`, or in up to 20 regions you name by their codes, such as `fra`. With `["*"]`, the regions are settled each time a deployment starts, so a new region joins at the next deployment. `min_replicas` sets how many replicas run in each region, from 1 to 5. `max_replicas` is saved with the app, but the number of replicas does not grow above `min_replicas`. ## Health checks A replica starts once its image is in place. We then send it `GET` requests for the app's health check path (`/` unless you set another) on the port in its `PORT` variable. An answer counts when it arrives within 5 seconds with a status from 200 to 399. We do not follow redirects, so a `301` or `302` counts as a pass. Two passes in a row make the replica *healthy*. A replica fails when its image is not in place within 20 minutes of being planned, or when it is not healthy within 10 minutes of starting. We replace a failed replica. A region fails when it has had two more failed replicas than `min_replicas` (three for one replica), or when it has no room for a replica for a minute. ## Going live A deployment goes live once every region has `min_replicas` healthy replicas. Every domain of the app then switches to the new replicas at once. The previous deployment's replicas stop taking requests, stop about a minute later, and are removed 30 minutes after they stopped taking requests. A rollback within that time starts them again instead of placing new ones. An app on `["*"]` goes live without a region that failed, as long as another region is ready. The deployment's `error` then names the region left out, as in `Left out sin: No app node in sin has room for another replica of this deployment`. An app with named regions goes live only when every one of them comes up, and otherwise fails with `Replicas could not start in …` and the reason for each region. ## While an app runs About once a minute, we check every serving replica's health check path. A replica that fails a check is marked `unhealthy` and keeps serving. After three failed checks in a row, it stops taking requests and we start a replacement in its region. When every replica of the app is failing, they all keep serving instead of leaving the app with none. We stop replacing replicas in a region once `min_replicas` plus two of them have failed within an hour, and start again when fewer have. Routes to an app carry WebSocket connections as well as ordinary requests. ## Redeploys and rollbacks A *redeploy* runs the current deployment's image again, with the app's settings and environment variables as they are now. A redeploy with a rebuild builds the app's branch again, or for an image app deploys its saved image reference again. A *rollback* is a new deployment of an earlier deployment's image. Only a deployment that went live can be rolled back to. It runs with the environment variables as they are now, in the app's regions as they are now, and build-time values stay as they were when that image was built. [Roll back to an earlier deployment](/docs/apps/roll-back-a-deployment/) has the details. ## What your app must do - Listen on the port in the `PORT` environment variable, on every interface (`0.0.0.0`). An app that listens only on `127.0.0.1` fails its health checks. We choose the port for each replica, and the app's `port` setting does not change it. - Answer `GET` on the health check path with a status from 200 to 399 within 5 seconds, once it is ready to take requests. - Keep nothing on its disk that must outlive a deployment. Use a database or [Object Storage](/docs/object-storage/). - Exit when it receives `SIGINT`, which is how we stop a replica. A replica that does not exit is stopped by force. Every replica also gets these variables, which you cannot set yourself: `PORT` : The port to listen on. `CORITAN_APP` : The app's name on the platform, such as `web-shop`. `CORITAN_REGION` : The code of the replica's region, such as `fra`. `CORITAN_DEPLOYMENT` : The ID of the deployment the replica runs. ## Related - [Create an app](/docs/apps/create-an-app/) - [Deploy an app](/docs/apps/deploy-an-app/) - [App and deployment fields](/docs/apps/fields/) - [Apps limits](/docs/apps/limits/) # Create an app > Create an app from a git repository or a container image, choose its size, regions and replicas, and start its first deployment. Source: https://www.coritan.com/docs/apps/create-an-app/ In the dashboard: - /dashboard/apps/new: https://www.coritan.com/dashboard/apps/new Create an app to run a web service from a git repository or from a container image. You create it on the dashboard's **New app** page, or through the API. Creating it also starts its first deployment unless you turn that off, so a working app is live once its replicas pass their health check. ## Before you begin - Sign in to the [dashboard](https://www.coritan.com/dashboard/apps). **Apps** is in the sidebar only while Apps is on for the platform ([Limits and availability](/docs/apps/#limits-and-availability)). - For a git app, the repository must be on a public host and reachable over HTTPS. For a private repository, create an access token on your git host that can read it. - For an image app, the image must be one that anyone can pull, such as a public image on GitHub Container Registry or Docker Hub. - Make sure the app listens on the port in its `PORT` variable and answers its health check path ([What your app must do](/docs/apps/how-apps-work/#what-your-app-must-do)). - For an organization's app, you need the owner or admin role in the organization. - You can have a set number of apps. The **Apps** list shows how many you have, such as `3 of 20 apps`, and [Regions, sizes and limits](/docs/apps/#regions-sizes-and-limits) shows how to read it through the API. ## Create an app in the dashboard 1. Go to **Apps** in the sidebar and select **New app…**. 2. Under **App**, type a **Name**, such as `Web shop`. The hint under it shows the address the app will answer at, such as `It answers at web-shop.apps.example.net, with a short suffix when that name is taken.` 3. Under **Source**, choose **Git repository** or **Ready image**. - For a git repository, fill in **Repository**, and change **Branch** when the app is not on `main`. Fill in **Directory** when the app is not at the top of the repository, and **Access token** when the repository is private. - For a ready image, fill in **Image**, such as `ghcr.io/example/web:1.4.0`. 4. Under **Regions**, choose **Every region**, or choose **The regions I choose** and tick each region you want. Then choose **Replicas per region**. 5. Under **Run**, check **Port**, **Health check path** and **Size**. 6. To change how a git app builds and starts, or to choose its name on the platform yourself, select **Show settings** on the last card. It is **Build and start** for a git app and **Name on the platform** for an image app. 7. Leave **Start the first deployment now** ticked to deploy as soon as the app exists, or untick it. > [!TIP] > The form has no environment variables. When the app needs some to build or start, untick **Start the first deployment now**, add them on the app's **Environment** tab, then deploy ([Set environment variables](/docs/apps/environment-variables/)). 8. Select **Create app**. When a field is wrong, the message under it says why, and the form keeps what you typed. Any other refusal appears above the buttons under `Could not create the app`. ## What you choose The **App** card: **Name** : What you call the app, up to 100 characters. You can change it later. The **Source** card. Whether the app uses a **Git repository** or a **Ready image** cannot change later: **Repository** : For a git app, the repository's HTTPS address, such as `https://github.com/example/web-shop`, with no user name, token, query or fragment in it. **Branch** : The branch to build, `main` at first. Pushes to it deploy the app once you set up the push webhook. **Directory** : Where the app lives inside the repository. Leave it empty when the app is at the top. **Access token** : For a private repository. We keep it encrypted, use it only to fetch the code, and never show it again. The app shows its last four characters so you can tell which token it holds. **Image** : For an image app. A reference such as `ghcr.io/example/web:1.4.0`, or one pinned to a digest such as `ghcr.io/example/web@sha256:…`. It cannot be in the platform's own registry, which only holds images built from git. The **Regions** card: **Every region** or **The regions I choose** : Every region that can run apps at the time of each deployment, which is the default, or up to 20 regions you tick. Each region shows its code and country. **Replicas per region** : How many replicas run in each region, from 1 to 5, 1 at first. The **Run** card: **Port** : A number from 1 to 65535, `3000` at first, saved with the app. Each replica gets the port to listen on in its `PORT` variable, whatever this setting says. **Health check path** : The path we request to decide that a replica is healthy, `/` at first. It starts with `/`, such as `/healthz`. **Size** : The memory, CPU and disk each replica gets. Each option shows all three ([Replicas and regions](/docs/apps/how-apps-work/#replicas-and-regions)). The **Build and start** card of a git app, under **Show settings**: **Framework** : A short lowercase label, such as `nextjs`, saved with the app. The build does not read it: Railpack works out the framework from the files. **Dockerfile** : A Dockerfile to build with, as a path inside the repository, such as `docker/Dockerfile`. Leave it empty to use a `Dockerfile` in the app's directory, or Railpack when there is none ([Builds](/docs/apps/how-apps-work/#builds)). **Install command**, **Build command** and **Start command** : Commands that replace the ones Railpack would choose, up to 500 characters each. A Dockerfile build does not use them. On the same card, and on the **Name on the platform** card of an image app: **Name on the platform** : The app's unique name, such as `web-shop`, which its platform name `web-shop.` is made from. It cannot change after you create the app ([Names on the platform](#names-on-the-platform)). ## Names on the platform A name on the platform has 3 to 63 lowercase letters, digits and hyphens, and starts and ends with a letter or a digit. It cannot have hyphens in both its third and fourth places, and no other app on the platform can have it. Some words are reserved for the platform, such as `api`, `admin`, `app`, `status` and `www`. When you leave it out, we make one from the app's name, such as `web-shop` for `Web shop`. When that one is taken or reserved, we add a hyphen and four random characters, as in `web-shop-k3x9`. ## What happens next The dashboard confirms with a message such as `Web shop created.` and opens the first deployment's page, where the build log follows the build ([Read a deployment and its build log](/docs/apps/read-a-deployment/)). With no first deployment, it opens the app's **Deployments** tab. A git app starts with a build of its branch, then starts replicas. An image app starts replicas at once. A git app also gets a push webhook, and its secret is shown once. In the dashboard, the **Push webhook secret** dialog shows it with the **Payload URL**: copy both, or select **Copy all**, then select **I have saved it**. Through the API, the secret is in the answer to the request that creates the app. Save it if you want pushes to deploy the app ([Deploy on every push](/docs/apps/push-webhook/)). The **Settings** tab keeps the address and the secret's last four characters, and you can make a new secret there later. If the first deployment cannot start, we still create the app and say why. The app's page shows `The first deployment did not start` with the reason and a **Deploy…** button. Fix the cause, then deploy it ([Deploy an app](/docs/apps/deploy-an-app/)). ## Result The app is in the **Apps** list with its platform address, and its first deployment is on its way unless you turned it off. Once that deployment is **Ready**, the app is **Live** and answers at `https://.`. ## Troubleshooting `The name web-shop is taken on the platform. Choose another` : Another app has that name on the platform. Choose another, or leave it empty so that we make one. `www is reserved. Choose another name` : The name is one the platform keeps for itself. Choose another. `Your account has as many apps as it may` or `The limit is 20 apps. Delete one before creating another` : You have as many apps as you may, so **New app…** and **Create app** are turned off. Delete one you no longer need ([Change or delete an app](/docs/apps/app-settings/#delete-an-app)). `The repository must be on a public host.` or `The repository URL must start with https://.` : Give the repository's HTTPS address on a public host. `Put the access token in the app's repository token, not in the URL.` : Remove the user name or token from the address and put the token in **Access token**. `The image must be on a public registry` : The image's registry is on a private network. Push the image to a public registry. `These regions cannot run apps: syd` : The regions named cannot run apps now. Choose from the regions the platform offers, or choose every region. `Too many apps created in the last hour. Wait a while and try again.` : You created 20 apps in the last hour. Wait, then try again. `Could not load the regions` : The page could not read the regions and sizes it offers. Select **Try again**. ## Related - [How Apps works](/docs/apps/how-apps-work/) - [Deploy an app](/docs/apps/deploy-an-app/) - [Set environment variables](/docs/apps/environment-variables/) - [Add a custom domain](/docs/apps/custom-domains/) ## With the API [`POST /api/v1/client/apps`](/docs/api/reference/client/apps/#op-post-api-v1-client-apps) creates an app of your own. For an organization's app, send the same body to [`POST /api/v1/orgs/{org_slug}/apps`](/docs/api/reference/organizations/organization-apps/#op-post-api-v1-orgs-org-slug-apps). ```bash curl -X POST https://api.coritan.com/api/v1/client/apps \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Web shop", "source_type": "git", "repo_url": "https://github.com/example/web-shop", "repo_branch": "main", "repo_token": "github_pat_EXAMPLE", "health_check_path": "/healthz", "instance_size": "small", "regions": ["fra", "iad"], "min_replicas": 2, "env": [{"key": "DATABASE_URL", "value": "postgres://app:secret@db.example.com/shop"}] }' ``` The body takes these fields. Only `name` is required, and a git app also needs `repo_url`, an image app `image_ref`. `name` : 1–100 characters. `slug` : The name on the platform. Made from `name` when left out. `source_type` : `git`, the default, or `image`. `repo_url`, `repo_branch`, `repo_subdir` and `repo_token` : A git app's repository, branch (`main` when left out), directory and access token. An image app refuses them. `image_ref` : An image app's image reference. A git app refuses it. `dockerfile_path`, `install_command`, `build_command`, `start_command` and `framework` : How a git app builds and starts. `port` and `health_check_path` : `3000` and `/` when left out. `instance_size` : `small`, `medium` or `large`. `small` when left out. `regions` : `["*"]` for every region, the default, or a list of up to 20 region codes. `min_replicas` and `max_replicas` : Replicas in each region, 1–5 for `min_replicas` (1 when left out). `max_replicas` takes up to 10, and one below `min_replicas` is raised to it. `env` : A list of variables, each with `key`, `value` and optionally `secret` and `target` ([Set environment variables](/docs/apps/environment-variables/#with-the-api)). `deploy` : `true`, the default, starts the first deployment. `false` creates the app without one. It answers `201`: ```json { "app": { "uuid": "3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21", "name": "Web shop", "slug": "web-shop", "status": "active", "source_type": "git", "repo_token_set": true, "repo_token_hint": "MPLE", "url": "https://web-shop.apps.example.net" }, "deployment": { "uuid": "0b6f3d2e-7a41-4c59-8e1d-2f9a6b3c4d5e", "status": "queued", "source": "git", "trigger": "manual", "git_ref": "main" }, "deploy_error": null, "webhook": { "url": "https://api.coritan.com/api/v1/apps/hooks/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21", "secret": "4f1d2c3b5a69788796a5b4c3d2e1f0a94f1d2c3b5a69788796a5b4c3d2e1f0a9" } } ``` The example shortens `app` and `deployment`, which carry every field ([App and deployment fields](/docs/apps/fields/)). `webhook.secret` appears in this answer only, and `webhook` is `null` for an image app. `deployment` is `null` when you sent `"deploy": false`, and also when the first deployment could not start, when `deploy_error` says why, as in `Builds are not available on this platform yet`. | Status | `detail` | Cause | | --- | --- | --- | | `409` | `{"error": "slug_taken", "message": "The name web-shop is taken on the platform. Choose another"}` | Another app has the `slug` you sent. | | `409` | `{"error": "app_limit", "message": "The limit is 20 apps. Delete one before creating another", "limit": 20}` | You have as many apps as you may. | | `422` | `The app name on the platform must be 3 to 63 lowercase letters, digits and hyphens, starting and ending with a letter or digit` | The `slug` breaks the rules above. | | `422` | `www is reserved. Choose another name` | The `slug` is reserved. | | `422` | `{"message": "These regions cannot run apps: syd", "unavailable": ["syd"], "available": ["fra", "iad"]}` | A region in `regions` cannot run apps now. | | `422` | `That is not an image reference Docker can pull, e.g. ghcr.io/you/app:1.2.0` | `image_ref` is not an image reference. | | `422` | `repo_url is for apps that build from git` | An image app was sent a repository field. | | `422` | `The health check path must start with / and be a plain URL path` | `health_check_path` is not a path. | | `429` | `{"error": "rate_limited", "message": "Too many apps created in the last hour. Wait a while and try again.", "retry_after_seconds": 3600}` | You created 20 apps in the last hour. | An organization's route also answers `403` with `Admin access required` for a member who is not an owner or admin. [Apps limits](/docs/apps/limits/#requests) lists the other rate limits. ## API - `POST /api/v1/client/apps`: Create an app and, unless deploy is false, its first deployment (https://www.coritan.com/docs/api/reference/client/apps/#op-post-api-v1-client-apps) - `POST /api/v1/orgs/{org_slug}/apps`: Create an app and, unless deploy is false, its first deployment (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-post-api-v1-orgs-org-slug-apps) # Deploy an app > Deploy a branch, tag or commit of a git app or a new image reference, redeploy to apply changed settings, and cancel a deployment. Source: https://www.coritan.com/docs/apps/deploy-an-app/ In the dashboard: - /dashboard/apps/…/deployments: https://www.coritan.com/dashboard/apps A deployment runs one version of an app. Deploy to ship a new version: a branch, a tag or a commit of a git app, or an image reference for an image app. Redeploy to run the current version again, which applies settings that only a new deployment picks up. Each deployment goes live only once its replicas are healthy, and the current one serves until then ([Deployments](/docs/apps/how-apps-work/#deployments)). In the dashboard, **Deploy…** and **Redeploy…** are in the app's header, and the **Deployments** tab lists every deployment. ## Before you begin - Open the app from **Apps** in the [dashboard](https://www.coritan.com/dashboard/apps). - The app must be `active`. While it is suspended, **Deploy…** and **Redeploy…** are turned off, and the API refuses to deploy it. - For an organization's app, you need the owner or admin role in the organization. - A push webhook can deploy each push to the app's branch for you ([Deploy on every push](/docs/apps/push-webhook/)). ## Deploy in the dashboard 1. Open the app and select **Deploy…**. 2. In **Branch, tag or commit**, type what to deploy ([Choose what to deploy](#choose-what-to-deploy)). It holds the app's branch at first, such as `main`. For an image app, the field is **Image reference** and holds the app's saved image. 3. Select **Deploy**. A message confirms `Deployment started.`, and the new deployment's page opens ([Read a deployment and its build log](/docs/apps/read-a-deployment/)). When the field is refused, the message under it says why. Any other refusal appears in the dialog under `Could not start the deployment`. ## Choose what to deploy A git app builds the version you name: The app's branch : The default. The build takes the newest commit on the branch when it starts. Another branch : Its name, such as `release`. The app keeps its own branch for pushes and redeploys. A tag : The full name of the tag, such as `refs/tags/v1.4.0`. A bare tag name, such as `v1.4.0`, is looked for as a branch and the build fails. A commit : Through the API only, as `git_sha`: its full ID, 40 characters (64 in a repository that uses SHA-256), with or without a branch. A shorter ID is ignored, and the build takes the newest commit on the branch instead. The dashboard's **Branch, tag or commit** field looks for a commit ID as a branch, so the build fails. We fetch the commit by its ID. If the host does not serve commits that way, we fetch the branch, and the build fails when the branch has moved past that commit. An image app runs its saved image unless you name another reference, such as `ghcr.io/example/web:1.5.0`. The reference you name runs for this deployment only, and the app keeps its saved image. To change the image the app keeps, change it on the **Settings** tab ([Change or delete an app](/docs/apps/app-settings/)). ## Redeploy A redeploy runs the image of the deployment that serves the app again, with the app's settings as they are now. It applies changes to the variables the running app reads, the size, the regions and the number of replicas. A redeploy with a rebuild builds the app's branch again, which applies changes to the repository, the branch, the directory, the access token, the build settings and the variables the build reads. For an image app, it deploys the image the app keeps, so after you change an image app's image, redeploy with a rebuild: a plain redeploy runs the image that is running now. An app that has nothing deployed yet always builds or pulls afresh. To redeploy in the dashboard: 1. Select **Redeploy…** in the app's header. It is there once a deployment serves the app. 2. To rebuild, tick **Build the branch again**. For an image app, the box is **Pull the image again**. 3. Select **Redeploy**. A message confirms `Redeploy started.`, and the new deployment's page opens. After you save a change that only a new deployment applies, a note under the app's header says so: `A redeploy applies the saved changes`, `A new build applies the saved changes`, or for an image app `The new image is not running yet`. Its **Redeploy…** button opens the same dialog, with the box ticked when the change needs a rebuild. Through the API, the answer to the change says which one it needs ([Change settings through the API](/docs/apps/app-settings/#change-settings-through-the-api)). ## Cancel a deployment You can cancel a deployment that is still `queued`, `building` or `deploying`. Its build stops, its replicas are removed, and its status becomes `canceled`. The deployment that serves the app goes on serving. A deployment that has finished cannot be cancelled. 1. On the **Deployments** tab, open the menu at the end of the deployment's row and select **Cancel deployment…**. On the deployment's own page, **Cancel deployment…** is in its header. 2. Select **Cancel deployment** to confirm, or **Keep it** to leave it running. A message confirms `Deployment cancelled.`, and the deployment shows **Cancelled**. ## Read the deployment history The **Deployments** tab lists the app's deployments, newest first, 20 to a page. Its card says how many there are, such as `14 deployments, newest first.` **Deployment** : The first line of the commit message, or what the deployment was, such as `Redeploy`, `Rollback` or `Build of main`. **Current** marks the one that serves the app. Under it are the short commit and the branch or tag, or the image. A failed deployment also shows why it failed. **Status** : **Queued**, **Building**, **Deploying**, **Ready**, **Failed**, **Cancelled** or **Superseded** ([Status words](/docs/apps/fields/#status-words)). While a deployment is on its way, the line under it says what it is doing, such as `Starting replicas` or `Waiting for a builder`. The current deployment's line says how many of its replicas are healthy. **Source** : What started it, such as `Push to main`, `Deploy of main` or `Rollback`, and who, such as `You` or `The push webhook`. **Created** : When it was created, and how long it took, such as `Took 3 min 12 s`, or `45 s so far` while it is on its way. Select a row to open the deployment's page. The menu at the end of each row has **Open deployment**, **Roll back to this…** for a version you can go back to ([Roll back to an earlier deployment](/docs/apps/roll-back-a-deployment/)), **Cancel deployment…** for one on its way, and **Copy deployment ID**. While a deployment is on its way, the first page updates on its own. ## Result The new deployment is first on the **Deployments** tab. It moves to **Ready** and serves the app once its replicas are healthy in every region, or it ends as **Failed** or **Cancelled** and the current deployment keeps serving. [Read a deployment and its build log](/docs/apps/read-a-deployment/) shows how to follow it. ## Troubleshooting `The app is suspended; it cannot deploy` : We suspended the app. [Contact support](/docs/support/conversations/) to find out why. `This app builds from git. Deploy a branch or a commit instead` : An image reference was sent for a git app. For an image app, the message says it runs an image. `git could not fetch v1.4.0 from https://github.com/example/web-shop.` : The branch does not exist. For a tag, give its full name, such as `refs/tags/v1.4.0`. For a commit, deploy it through the API with `git_sha`. For a private repository, check that the access token can still read it. `release has moved past commit 9f2c1e7b4a6d, and github.com does not serve an older commit by its id. Deploy the branch again.` : The host would not send the commit by its ID, and the branch no longer ends at it. Deploy the branch, or push a branch that ends at that commit. `The deployment is ready; only a deployment on its way can be canceled` : The deployment has finished. To stop serving it, deploy another version or roll back ([Roll back to an earlier deployment](/docs/apps/roll-back-a-deployment/)). `Too many deployments of this app. Wait a few minutes.` : The app started 30 deployments in the last 10 minutes. Wait, then deploy again. ## Related - [Read a deployment and its build log](/docs/apps/read-a-deployment/) - [Roll back to an earlier deployment](/docs/apps/roll-back-a-deployment/) - [Deploy on every push](/docs/apps/push-webhook/) - [How Apps works](/docs/apps/how-apps-work/) ## With the API Each request takes the app's `uuid`. For an organization's app, send the same request under `/api/v1/orgs/{org_slug}/apps` ([organization apps reference](/docs/api/reference/organizations/organization-apps/#op-post-api-v1-orgs-org-slug-apps-app-uuid-deployments)). ### Deploy through the API [`POST /api/v1/client/apps/{app_uuid}/deployments`](/docs/api/reference/client/apps/#op-post-api-v1-client-apps-app-uuid-deployments) starts a deployment. An empty body deploys the app's branch or its saved image. `git_ref` : For a git app. A branch name, or a full ref such as `refs/tags/v1.4.0`. The app's branch when left out. `git_sha` : For a git app. A commit's full ID. `image_ref` : For an image app. The image to run this time. The app's saved image when left out. ```bash curl -X POST https://api.coritan.com/api/v1/client/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21/deployments \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"git_ref": "refs/tags/v1.4.0"}' ``` It answers `201` with the new deployment: ```json { "deployment": { "uuid": "0b6f3d2e-7a41-4c59-8e1d-2f9a6b3c4d5e", "status": "queued", "source": "git", "trigger": "manual", "git_ref": "refs/tags/v1.4.0", "git_sha": null, "image_ref": null, "regions": null, "error": null, "is_current": false, "in_flight": true, "can_rollback": false, "replicas": {"healthy": 0, "unhealthy": 0, "starting": 0, "total": 0}, "build": {"uuid": "e2a9c4d1-3b5f-4e7a-9c8d-1f2e3a4b5c6d", "status": "queued", "strategy": null, "attempts": 0}, "created_at": "2026-09-26T10:05:00Z" } } ``` The example leaves some fields out. [App and deployment fields](/docs/apps/fields/#deployment) describes each one. `regions` stays `null` until the deployment starts placing replicas. | Status | `detail` | Cause | | --- | --- | --- | | `409` | `{"error": "source_mismatch", "message": "This app runs an image. Deploy an image reference instead"}` | A branch or commit was sent for an image app, or an image for a git app. | | `409` | `{"error": "app_not_active", "message": "The app is suspended; it cannot deploy"}` | The app is suspended. | | `422` | `Give a branch or commit, or an image reference, not both` | The body held both. | | `422` | `{"error": "repository_invalid", "message": "'release..x' is not a branch name git accepts."}` | `git_ref`, or the app's repository settings, cannot be fetched. | | `503` | `{"error": "registry_not_configured", "message": "Builds are not available on this platform yet."}` | The platform cannot build images at the moment. An image app can still deploy. | | `429` | `{"error": "rate_limited", "message": "Too many deployments of this app. Wait a few minutes.", "retry_after_seconds": 600}` | The app started 30 deployments in the last 10 minutes. | ### Redeploy through the API [`POST /api/v1/client/apps/{app_uuid}/redeploy`](/docs/api/reference/client/apps/#op-post-api-v1-client-apps-app-uuid-redeploy) runs the current version again. Send `{"rebuild": true}` to build the branch again, or for an image app to deploy its saved image. The body can be left out. ```bash curl -X POST https://api.coritan.com/api/v1/client/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21/redeploy \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"rebuild": false}' ``` It answers `201` with the new deployment, as above. A redeploy that reuses the image has `source` `redeploy` and starts as `deploying`. It refuses a suspended app and counts towards the same limit of 30 deployments in 10 minutes. ### Cancel through the API [`POST /api/v1/client/apps/{app_uuid}/deployments/{deployment_uuid}/cancel`](/docs/api/reference/client/apps/#op-post-api-v1-client-apps-app-uuid-deployments-deployment-uuid-cancel) cancels a deployment on its way, and answers with it, now `canceled`, and its replicas: ```bash curl -X POST https://api.coritan.com/api/v1/client/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21/deployments/0b6f3d2e-7a41-4c59-8e1d-2f9a6b3c4d5e/cancel \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` A deployment that has finished answers `409` with `The deployment is ready; only a deployment on its way can be canceled`, naming its status. One that belongs to another app answers `404` with `Deployment not found`. ### List deployments [`GET /api/v1/client/apps/{app_uuid}/deployments`](/docs/api/reference/client/apps/#op-get-api-v1-client-apps-app-uuid-deployments) lists the app's deployments, newest first: ```bash curl "https://api.coritan.com/api/v1/client/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21/deployments?limit=20&offset=0" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "deployments": [ {"uuid": "0b6f3d2e-7a41-4c59-8e1d-2f9a6b3c4d5e", "status": "ready", "source": "git", "trigger": "manual", "is_current": true}, {"uuid": "8c4e1a7d-2f9b-4d6e-b3a8-6e1f9c2d5a10", "status": "superseded", "source": "git", "trigger": "push", "is_current": false} ], "total": 14, "limit": 20, "offset": 0 } ``` `limit` takes 1–100 (20 when left out), `offset` skips that many deployments, and `total` counts them all. Each deployment carries every field, shortened here. ## API - `GET /api/v1/client/apps/{app_uuid}/deployments`: List an app's deployments, newest first, a page at a time (https://www.coritan.com/docs/api/reference/client/apps/#op-get-api-v1-client-apps-app-uuid-deployments) - `POST /api/v1/client/apps/{app_uuid}/deployments`: Create app deployment (https://www.coritan.com/docs/api/reference/client/apps/#op-post-api-v1-client-apps-app-uuid-deployments) - `POST /api/v1/client/apps/{app_uuid}/deployments/{deployment_uuid}/cancel`: Cancel a deployment that is still queued, building or deploying (https://www.coritan.com/docs/api/reference/client/apps/#op-post-api-v1-client-apps-app-uuid-deployments-deployment-uuid-cancel) - `POST /api/v1/client/apps/{app_uuid}/redeploy`: Redeploy app (https://www.coritan.com/docs/api/reference/client/apps/#op-post-api-v1-client-apps-app-uuid-redeploy) - `GET /api/v1/orgs/{org_slug}/apps/{app_uuid}/deployments`: List an app's deployments, newest first, a page at a time (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-get-api-v1-orgs-org-slug-apps-app-uuid-deployments) - `POST /api/v1/orgs/{org_slug}/apps/{app_uuid}/deployments`: Create app deployment (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-post-api-v1-orgs-org-slug-apps-app-uuid-deployments) - `POST /api/v1/orgs/{org_slug}/apps/{app_uuid}/deployments/{deployment_uuid}/cancel`: Cancel a deployment that is still queued, building or deploying (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-post-api-v1-orgs-org-slug-apps-app-uuid-deployments-deployment-uuid-cancel) - `POST /api/v1/orgs/{org_slug}/apps/{app_uuid}/redeploy`: Redeploy app (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-post-api-v1-orgs-org-slug-apps-app-uuid-redeploy) # Read a deployment and its build log > Follow a deployment as it builds and starts, read the end of its build log, and check the health of each replica. Source: https://www.coritan.com/docs/apps/read-a-deployment/ In the dashboard: - /dashboard/apps/…/deployments: https://www.coritan.com/dashboard/apps Read a deployment to follow it while it builds and starts, to find out why it failed, and to check the health of each of its replicas. Each deployment has its own page in the dashboard, and the API returns the same facts. Every member of an organization can read its apps' deployments. ## Before you begin - In the [dashboard](https://www.coritan.com/dashboard/apps), open the app. Through the API, find the deployment's ID in the app's history ([List deployments](/docs/apps/deploy-an-app/#list-deployments)). The five newest also come with the app itself. - [Deployments](/docs/apps/how-apps-work/#deployments) explains what each status means. ## Open a deployment 1. Open the app from **Apps** and select the **Deployments** tab. 2. Select the deployment's row, or **Open deployment** in the menu at the end of it. When you start a deployment in the dashboard, its page opens on its own. **All deployments** goes back to the list. While the deployment is on its way, the page reads it again every few seconds, and the build log follows the build. ## What the page shows The header : The first line of the commit message, or what the deployment was, with its status and **Current** when it serves the app. Under it are its short ID with **Copy deployment ID**, the short commit or the branch, what started it, such as `Push to main`, and when. **Roll back to this…** and **Cancel deployment…** appear when you can use them. Its progress : While it is on its way, a bar says what it is doing, such as `Waiting for a builder`, `Building` or `Starting replicas`, and how long it has taken so far. How it ended : `The deployment failed` with the reason, `The deployment was cancelled`, or `A newer deployment replaced this one`. A deployment that went live without a region shows `It went live without every region` with the region it left out and why. **Build log** : The end of the build log ([The build log](#the-build-log)). **Replicas** : Every replica the deployment has had ([Replicas](#replicas)). **Details** : **Source**, **Started by**, **Branch or tag**, **Commit** and **Author** for a git deployment, and **Image** and **Digest** once it has an image. **Regions** lists where it runs. **Build** gives the build's status, how it built (`From its Dockerfile` or `Detected by the builder`) and the attempt when it took more than one, and **Build time** how long it took. **Created**, **Ready** and **Ended** give the times. ## The build log A git deployment keeps the last 64 KB of its build log. The log hides the repository's access token and every secret build value of six characters or more, which appear as `[redacted]`. An image deployment has no build, so it has no log, and a redeploy or a rollback that reuses an image has none either. The **Build log** card shows the last 40 lines, where a failure usually is. The button at the top of the card, such as `Show all 312 lines`, shows every line that is kept, and **Copy the log lines shown** copies what you see. The card's description says how the build ended and how long it took, such as `Built in 2 min 37 s.`, or `Only the end is kept` when the build wrote more than 64 KB. ## Replicas The **Replicas** card lists every replica the deployment has had, with its region, such as `Frankfurt (fra)`, its state, when it was last checked, its last error and when it started. A replica that has failed health checks says how many, such as `2 failed checks`. | State | Meaning | | --- | --- | | **Provisioning** | Placed in its region, with its image on the way. | | **Starting** | Started, and waiting for two health checks in a row to pass. | | **Healthy** | Passing its health checks, and serving when its deployment is live. | | **Unhealthy** | Failed its most recent health checks. It keeps serving until it fails three in a row. | | **Draining** | No longer taking requests. It stops about a minute later. | | **Stopped** | Stopped. It is removed 30 minutes after it stopped taking requests, unless a rollback starts it again first. | | **Failed** | Did not start in time, failed three health checks in a row, or was removed after it stopped. | The API sends each state in lower case, such as `healthy`. A replica's last error says what went wrong, such as `Health check on /healthz: HTTP 500` or `The app did not pass its health check within 10 minutes. Health check on /: the connection failed`. ## Result You know where the deployment is, and why when it failed. [Troubleshoot Apps](/docs/apps/troubleshooting/) lists the common causes and their fixes. ## Troubleshooting `Deployment not found` or `This deployment is not on this app` : The ID belongs to another app's deployment, or to none. Check both IDs, or open the deployment from the app's **Deployments** tab. The log is empty : An image deployment has no log (`Image deployments have no build log`), nor does a deployment that reused an image (`Nothing was built for this deployment`). A build that has not started has written nothing yet (`No log yet`). The log starts partway through : The build wrote more than 64 KB, and only the end is kept. ## Related - [Deploy an app](/docs/apps/deploy-an-app/) - [Roll back to an earlier deployment](/docs/apps/roll-back-a-deployment/) - [App and deployment fields](/docs/apps/fields/) - [Troubleshoot Apps](/docs/apps/troubleshooting/) ## With the API For an organization's app, send the same requests under `/api/v1/orgs/{org_slug}/apps` ([organization apps reference](/docs/api/reference/organizations/organization-apps/#op-get-api-v1-orgs-org-slug-apps-app-uuid-deployments-deployment-uuid)). ### Read a deployment through the API [`GET /api/v1/client/apps/{app_uuid}/deployments/{deployment_uuid}`](/docs/api/reference/client/apps/#op-get-api-v1-client-apps-app-uuid-deployments-deployment-uuid) returns the deployment with every replica it has had in `replica_list`: ```bash curl https://api.coritan.com/api/v1/client/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21/deployments/1d7b4e9a-5c2f-4a8e-9d6b-2f8e5a1c7b11 \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "deployment": { "uuid": "1d7b4e9a-5c2f-4a8e-9d6b-2f8e5a1c7b11", "status": "failed", "source": "git", "trigger": "push", "git_ref": "main", "git_sha": "9f2c1e7b4a6d8e0f1a2b3c4d5e6f708192a3b4c5", "commit_message": "Move the health check to /healthz", "commit_author": "Alex Example", "image_ref": "registry.example.net/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21:1d7b4e9a-5c2f-4a8e-9d6b-2f8e5a1c7b11", "image_digest": "sha256:5b2d0c9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c", "regions": ["fra", "iad"], "actor_type": "webhook", "error": "Replicas could not start in fra: The app did not pass its health check within 10 minutes. Health check on /healthz: HTTP 404", "is_current": false, "in_flight": false, "can_rollback": false, "replicas": {"healthy": 0, "unhealthy": 0, "starting": 0, "total": 0}, "build": { "uuid": "e2a9c4d1-3b5f-4e7a-9c8d-1f2e3a4b5c6d", "status": "succeeded", "strategy": "railpack", "error": null, "attempts": 1, "log_bytes": 48211, "started_at": "2026-09-26T10:05:04Z", "finished_at": "2026-09-26T10:07:41Z", "created_at": "2026-09-26T10:05:00Z" }, "ready_at": null, "promoted_at": null, "finished_at": "2026-09-26T10:18:02Z", "created_at": "2026-09-26T10:05:00Z", "updated_at": "2026-09-26T10:18:02Z", "replica_list": [ { "location": "fra", "state": "failed", "health_failures": 0, "last_health_at": null, "last_error": "The app did not pass its health check within 10 minutes. Health check on /healthz: HTTP 404", "created_at": "2026-09-26T10:07:45Z", "drained_at": null } ] } } ``` The example shows one replica of several. [App and deployment fields](/docs/apps/fields/#deployment) describes every field. These say the most: The version : A git deployment has the branch or ref it built and the ID of the commit it checked out. A deployment started by a push also has the commit's message and author. Every deployment that has an image shows its reference and, once known, its digest. Where it came from : `source` is `git`, `image`, `redeploy` or `rollback`. `trigger` is `push` for the push webhook, `rollback` for a rollback, and `manual` for a request from you or a member of your organization. `actor_type` is `user` for a person signed in to Coritan, `staff` for a staff console session, `webhook` for a push, and `admin` when Coritan staff started it. Its progress : `status`, `in_flight` while it is on its way, `is_current` while it serves the app, and `can_rollback`. `replicas` counts the replicas in `provisioning`, `starting`, `healthy` and `unhealthy`: `starting` counts the first two, and `total` all four. What went wrong : When a deployment fails or is cancelled, its `error` says why. A deployment that went live without a region also says so there. The build : For a git deployment, its build's `status` (`queued`, `running`, `succeeded`, `failed` or `canceled`), its `strategy` (`dockerfile` or `railpack`), how many `attempts` it took, and when it started and finished. A build that stops reporting is tried once more. In each replica, `health_failures` counts its failed health checks in a row, and `drained_at` is when it stopped taking requests. ### Read the build log through the API [`GET /api/v1/client/apps/{app_uuid}/deployments/{deployment_uuid}/log`](/docs/api/reference/client/apps/#op-get-api-v1-client-apps-app-uuid-deployments-deployment-uuid-log) returns the end of the build log: ```bash curl https://api.coritan.com/api/v1/client/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21/deployments/1d7b4e9a-5c2f-4a8e-9d6b-2f8e5a1c7b11/log \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "build": {"uuid": "e2a9c4d1-3b5f-4e7a-9c8d-1f2e3a4b5c6d", "status": "succeeded", "strategy": "railpack"}, "log": "[builder] Fetching https://github.com/example/web-shop main at 9f2c1e7b4a6d.\n[builder] Checked out 9f2c1e7b4a6d.\n[builder] Planning the build with Railpack.\n...", "log_bytes": 48211, "truncated": false } ``` `log` is the text, up to 64 KB. `log_bytes` is how much the build wrote in all, and `truncated` is `true` when that is more than `log` holds. An image deployment answers `{"build": null, "log": "", "log_bytes": 0, "truncated": false}`. The log grows while the build runs, so read it again to see new lines. ## API - `GET /api/v1/client/apps/{app_uuid}/deployments/{deployment_uuid}`: Get one deployment of an app, with each of its replicas (https://www.coritan.com/docs/api/reference/client/apps/#op-get-api-v1-client-apps-app-uuid-deployments-deployment-uuid) - `GET /api/v1/client/apps/{app_uuid}/deployments/{deployment_uuid}/log`: The end of the deployment's build log (secrets redacted as it was written) (https://www.coritan.com/docs/api/reference/client/apps/#op-get-api-v1-client-apps-app-uuid-deployments-deployment-uuid-log) - `GET /api/v1/orgs/{org_slug}/apps/{app_uuid}/deployments/{deployment_uuid}`: Get one deployment of an app, with each of its replicas (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-get-api-v1-orgs-org-slug-apps-app-uuid-deployments-deployment-uuid) - `GET /api/v1/orgs/{org_slug}/apps/{app_uuid}/deployments/{deployment_uuid}/log`: The end of the deployment's build log (secrets redacted as it was written) (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-get-api-v1-orgs-org-slug-apps-app-uuid-deployments-deployment-uuid-log) # Roll back to an earlier deployment > Run an earlier deployment's image again when a new version misbehaves, without waiting for a build. Source: https://www.coritan.com/docs/apps/roll-back-a-deployment/ In the dashboard: - /dashboard/apps/…/deployments: https://www.coritan.com/dashboard/apps Roll back when a new version misbehaves and you want the one before it back. A rollback is a new deployment of an earlier deployment's image, so it skips the build and goes live as soon as its replicas are healthy. ## Before you begin - The app must be `active`. - The deployment you roll back to must have gone live at some point, and must not be the one serving the app now. In the dashboard, only such a deployment offers **Roll back to this…**. Through the API, its `can_rollback` field says whether it qualifies. - For an organization's app, you need the owner or admin role in the organization. ## Roll back in the dashboard 1. Open the app from **Apps** in the [dashboard](https://www.coritan.com/dashboard/apps) and select the **Deployments** tab. 2. Open the menu at the end of the deployment you want back and select **Roll back to this…**. On the deployment's own page, **Roll back to this…** is in its header. 3. The dialog names the deployment by its short commit, such as `Roll back to 3e7d2c1?`. Select **Roll back**. A message confirms `Rollback started.`, and the new deployment's page opens. The deployment serving now keeps serving until the new one is ready. ## What a rollback runs A rollback runs the earlier deployment's image, so the code and every value the build read are as they were when that image was built. The variables the running app reads, the regions and the number of replicas in each come from the app as it is now. Replicas of the earlier deployment that we have not removed yet start again instead of new ones, which makes the rollback quicker. We remove a replica 30 minutes after it stops taking requests ([Going live](/docs/apps/how-apps-work/#going-live)). The push webhook keeps working after a rollback, so the next push to the app's branch deploys that push. To keep the old version running, stop pushing to the branch, or fix the problem and push the fix. ## Result A new deployment appears first in the app's history, with the earlier deployment's commit and `Rollback` as its source. Through the API, its `source` and `trigger` are both `rollback`. It goes live once its replicas are healthy, like any other deployment, and the one it replaces becomes **Superseded**. If the rollback fails, the deployment that served the app keeps serving. ## Troubleshooting `Only a deployment that was ready once can be rolled back to` : The deployment never went live, so it has no image that ran. Choose one that offers **Roll back to this…**, or whose `can_rollback` is `true`. `That deployment is already serving` : It is the current deployment. There is nothing to roll back. `The app is suspended; it cannot deploy` : We suspended the app. [Contact support](/docs/support/conversations/) to find out why. `These regions cannot run apps: syd` : A region the app names cannot run apps now. Change the app's regions ([Change the settings](/docs/apps/app-settings/#change-the-settings)), then roll back again. `No region can run apps right now` : The app runs in every region, and no region can take replicas at the moment. Try again later. ## Related - [Deploy an app](/docs/apps/deploy-an-app/) - [Read a deployment and its build log](/docs/apps/read-a-deployment/) - [How Apps works](/docs/apps/how-apps-work/#redeploys-and-rollbacks) ## With the API [`POST /api/v1/client/apps/{app_uuid}/deployments/{deployment_uuid}/rollback`](/docs/api/reference/client/apps/#op-post-api-v1-client-apps-app-uuid-deployments-deployment-uuid-rollback) rolls the app back to the deployment in the path. For an organization's app, send it to [`POST /api/v1/orgs/{org_slug}/apps/{app_uuid}/deployments/{deployment_uuid}/rollback`](/docs/api/reference/organizations/organization-apps/#op-post-api-v1-orgs-org-slug-apps-app-uuid-deployments-deployment-uuid-rollback). ```bash curl -X POST https://api.coritan.com/api/v1/client/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21/deployments/8c4e1a7d-2f9b-4d6e-b3a8-6e1f9c2d5a10/rollback \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` It answers `201` with the new deployment: ```json { "deployment": { "uuid": "6a2f9d1c-4e8b-4b7a-a5c3-9d0e1f2a3b4c", "status": "deploying", "source": "rollback", "trigger": "rollback", "git_ref": "main", "git_sha": "3e7d2c1b0a9f8e7d6c5b4a3928170f6e5d4c3b2a", "image_ref": "registry.example.net/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21:8c4e1a7d-2f9b-4d6e-b3a8-6e1f9c2d5a10", "regions": ["fra", "iad"], "is_current": false, "in_flight": true } } ``` The example shortens the deployment ([App and deployment fields](/docs/apps/fields/#deployment)). | Status | `detail` | Cause | | --- | --- | --- | | `404` | `Deployment not found` | The deployment belongs to another app, or does not exist. | | `409` | `{"error": "not_rollbackable", "message": "Only a deployment that was ready once can be rolled back to"}` | The deployment never went live. | | `409` | `{"error": "already_current", "message": "That deployment is already serving"}` | It is the current deployment. | | `409` | `{"error": "app_not_active", "message": "The app is suspended; it cannot deploy"}` | The app is suspended. | | `409` | `No region can run apps right now` | The app runs in every region, and none can take replicas. | | `422` | `{"message": "These regions cannot run apps: syd", "unavailable": ["syd"], "available": ["fra", "iad"]}` | A region the app names cannot run apps now. | | `429` | `{"error": "rate_limited", "message": "Too many deployments of this app. Wait a few minutes.", "retry_after_seconds": 600}` | The app started 30 deployments in the last 10 minutes. | ## API - `POST /api/v1/client/apps/{app_uuid}/deployments/{deployment_uuid}/rollback`: Rollback app (https://www.coritan.com/docs/api/reference/client/apps/#op-post-api-v1-client-apps-app-uuid-deployments-deployment-uuid-rollback) - `POST /api/v1/orgs/{org_slug}/apps/{app_uuid}/deployments/{deployment_uuid}/rollback`: Rollback app (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-post-api-v1-orgs-org-slug-apps-app-uuid-deployments-deployment-uuid-rollback) # Set environment variables > Give an app settings and secrets as environment variables for its build, its replicas or both, and apply them with a deployment. Source: https://www.coritan.com/docs/apps/environment-variables/ In the dashboard: - /dashboard/apps/…/environment: https://www.coritan.com/dashboard/apps Environment variables give an app its settings and secrets, such as a database address or a key for another service. Each variable reaches the build, the running app or both. You set them on the app's **Environment** tab or through the API. Once you save a value, neither the dashboard nor the API shows it again. ## Before you begin - For an organization's app, you need the owner or admin role in the organization to change variables. Every member can see their names. - A change reaches the app with its next deployment ([Apply a change](#apply-a-change)). ## Add a variable 1. Open the app from **Apps** in the [dashboard](https://www.coritan.com/dashboard/apps) and select the **Environment** tab. 2. Select **Add variable…**. 3. In **Name**, type the name, such as `DATABASE_URL`. In **Value**, type or paste the value. 4. Leave **Secret** ticked to hide the value in build logs, or untick it for a plain value ([Secret and plain values](#secret-and-plain-values)). 5. In **Available at**, choose **Build and runtime**, **Runtime only** or **Build only** ([Where a variable reaches](#where-a-variable-reaches)). For an image app, it starts at **Runtime only**. 6. Select **Add variable**. A message confirms `DATABASE_URL added.`, and a note under the app's header says which kind of deployment applies it. ## Change or delete a variable To give a variable a new value, open the menu at the end of its row and select **Replace value…**. Type the new value, change **Secret** or **Available at** if you need to, and select **Save variable**. Leave **Value** empty to keep the saved value while you change the other two. A message confirms `DATABASE_URL saved.` A name cannot change. To rename a variable, delete it and add it again. To delete a variable, select **Delete variable…** in its menu, then **Delete variable** to confirm. A message confirms `DATABASE_URL deleted.` Running replicas keep the variable until the next deployment, and the value cannot be recovered. ## Read the variable list **Name** : The variable's name, and `Secret, hidden in build logs` or `Plain`. **Value** : `•••• shop` for a value of eight characters or more, where `shop` is its last four characters, or `Sealed` for a shorter one. **Available at** : **Build and runtime**, **Runtime only** or **Build only**. **Updated** : When the variable last changed. The card says how many variables the app has out of its limit, such as `12 of 200 variables. Values are sealed and never shown again.` With more than six, **Search variables** finds them by name. ## Names and values A name has upper-case letters, digits and underscores, does not start with a digit, and has at most 128 characters, such as `DATABASE_URL`. These names are set by the platform, so you cannot use them: - `PORT`, `TZ`, `STARTUP`, `SERVER_MEMORY`, `SERVER_IP` and `SERVER_PORT` - any name that starts with `CORITAN_` or `P_SERVER_` A value can be empty, and holds up to 32 KiB. The dashboard does not save an empty value, so set one through the API. An app has up to 200 variables, and their names and values together hold up to 256 KiB. ## Where a variable reaches Each variable has a target: Build and runtime : The build and every replica get it. New variables have this target unless you choose another. Runtime only : Only the replicas get it. Build only : Only the build gets it. [Builds](/docs/apps/how-apps-work/#builds) explains how a Railpack build and a Dockerfile build read it. An image app is not built, so only the variables its replicas get reach it. Every replica also gets `PORT` and the `CORITAN_` variables ([What your app must do](/docs/apps/how-apps-work/#what-your-app-must-do)). ## Secret and plain values A variable is *secret* unless you say otherwise. A secret value of six characters or more appears as `[redacted]` in the build log, and a Dockerfile build gets it as a build secret instead of a build argument. A plain value can appear in the build log as it is. Both kinds are stored encrypted, and neither is ever shown again after you save it. For a value of eight characters or more, the variable shows its last four characters, so you can tell which value is saved. To change a value, replace it. ## Apply a change Replicas keep the variables they started with. After you change a variable: - For one that the running app reads, redeploy the app. - For one that the build reads, redeploy with a rebuild, so the build runs again. [Redeploy](/docs/apps/deploy-an-app/#redeploy) shows both. In the dashboard, a note under the app's header says which one: `A redeploy applies the saved changes`, or `A new build applies the saved changes` for a variable the build reads. Its **Redeploy…** button starts it. Through the API, the answer to each change says `redeploy_required` when a redeploy is needed. ## A value that cannot be read Rarely, a saved value can no longer be opened on the platform, and the variable is marked as not readable. The running app starts without it, and a build that needs it fails with `The build variable API_KEY cannot be read on this platform. Save it again.` Set the variable again with its value. In the dashboard, the **Environment** tab says `One variable cannot be read`, and the variable's value reads `Unreadable, set it again`. Select **Set again…** in its menu, type the value, and select **Save variable**. ## Result The **Environment** tab lists each name with where it is available, whether it is secret, and the hint of its value. The next deployment uses them. ## Troubleshooting `Use upper-case letters, digits and underscores, not starting with a digit, such as DATABASE_URL.` or `'database_url' is not a variable name: upper-case letters, digits and underscores, not starting with a digit, at most 128 characters` : Change the name, for example to `DATABASE_URL`. `PORT is set by the platform. Choose another name.` or `PORT is set by the platform and cannot be changed` : The platform sets that name on every replica. Use another name, and read `PORT` to know where to listen. `API_KEY needs a value` : A new variable was saved without a value. Give it one. `The environment is larger than 256 KiB in total` : Shorten or delete some values. A large file is better kept in [Object Storage](/docs/object-storage/) and fetched when the app starts. A variable does not change in the running app : Replicas keep the variables they started with. Redeploy the app, with a rebuild for a variable the build reads. ## Related - [Deploy an app](/docs/apps/deploy-an-app/) - [How Apps works](/docs/apps/how-apps-work/#builds) - [Apps limits](/docs/apps/limits/) ## With the API For an organization's app, send the same requests under `/api/v1/orgs/{org_slug}/apps` ([organization apps reference](/docs/api/reference/organizations/organization-apps/#op-get-api-v1-orgs-org-slug-apps-app-uuid-env)). In each request, `target` is `both`, `runtime` or `build`. ### List variables [`GET /api/v1/client/apps/{app_uuid}/env`](/docs/api/reference/client/apps/#op-get-api-v1-client-apps-app-uuid-env) lists the variables by name, without their values: ```bash curl https://api.coritan.com/api/v1/client/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21/env \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "vars": [ { "key": "DATABASE_URL", "secret": true, "target": "runtime", "value": null, "hint": "shop", "readable": true, "created_at": "2026-09-20T09:12:00Z", "updated_at": "2026-09-24T16:40:00Z" }, { "key": "NODE_ENV", "secret": false, "target": "both", "value": null, "hint": null, "readable": true, "created_at": "2026-09-20T09:12:00Z", "updated_at": "2026-09-20T09:12:00Z" } ] } ``` `value` is always `null`. `hint` is the last four characters of a value of eight characters or more, and `null` otherwise. `readable` is `false` when the saved value can no longer be opened. ### Set one variable [`PUT /api/v1/client/apps/{app_uuid}/env/{key}`](/docs/api/reference/client/apps/#op-put-api-v1-client-apps-app-uuid-env-key) creates the variable or changes it. The body takes `value`, `secret` and `target`. A new variable needs `value`. For one that exists, leave `value` out to keep the saved value while you change `secret` or `target`. ```bash curl -X PUT https://api.coritan.com/api/v1/client/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21/env/STRIPE_KEY \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"value": "sk_live_EXAMPLE0000", "target": "runtime"}' ``` ```json { "var": {"key": "STRIPE_KEY", "secret": true, "target": "runtime", "value": null, "hint": "0000", "readable": true}, "created": true, "redeploy_required": true } ``` `created` is `true` for a new variable. The example shortens `var`. ### Set many variables [`PUT /api/v1/client/apps/{app_uuid}/env`](/docs/api/reference/client/apps/#op-put-api-v1-client-apps-app-uuid-env) sets up to 200 variables in one request. Each item in `vars` has a `key` and the same `value`, `secret` and `target` as above. With `"replace": true`, every variable not in the list is deleted. ```bash curl -X PUT https://api.coritan.com/api/v1/client/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21/env \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"vars": [{"key": "NODE_ENV", "value": "production", "secret": false}, {"key": "DATABASE_URL"}], "replace": true}' ``` ```json { "created": [], "updated": ["NODE_ENV"], "deleted": ["STRIPE_KEY"], "redeploy_required": true } ``` `created`, `updated` and `deleted` name the variables the request touched. `DATABASE_URL` kept its value because the request left `value` out. The answer also holds `vars`, every variable after the change, as in the list above. ### Delete a variable [`DELETE /api/v1/client/apps/{app_uuid}/env/{key}`](/docs/api/reference/client/apps/#op-delete-api-v1-client-apps-app-uuid-env-key) deletes one variable and answers `{"deleted": "STRIPE_KEY", "redeploy_required": true}`. Running replicas keep it until the next deployment. ### Errors | Status | `detail` | Cause | | --- | --- | --- | | `404` | `Variable not found` | The app has no variable with that name. | | `422` | `{"message": "PORT is set by the platform and cannot be changed", "field": "key"}` | The name is one the platform sets. | | `422` | `{"message": "API_KEY needs a value", "field": "value"}` | A new variable came without `value`. | | `422` | `{"message": "The value of API_KEY is larger than 32 KiB", "field": "value"}` | The value is too long. | | `422` | `{"message": "API_KEY appears twice", "field": "key"}` | `vars` names one variable twice. | | `422` | `An app can have at most 200 variables` | The change would leave the app with more than 200. | | `422` | `The environment is larger than 256 KiB in total` | The names and values would hold more than 256 KiB. | ## API - `GET /api/v1/client/apps/{app_uuid}/env`: List an app's environment variables without their values (https://www.coritan.com/docs/api/reference/client/apps/#op-get-api-v1-client-apps-app-uuid-env) - `PUT /api/v1/client/apps/{app_uuid}/env`: Set many variables; a variable given without value keeps its saved one (https://www.coritan.com/docs/api/reference/client/apps/#op-put-api-v1-client-apps-app-uuid-env) - `PUT /api/v1/client/apps/{app_uuid}/env/{key}`: Set one environment variable, creating it when it is new (https://www.coritan.com/docs/api/reference/client/apps/#op-put-api-v1-client-apps-app-uuid-env-key) - `DELETE /api/v1/client/apps/{app_uuid}/env/{key}`: Delete one environment variable (https://www.coritan.com/docs/api/reference/client/apps/#op-delete-api-v1-client-apps-app-uuid-env-key) - `GET /api/v1/orgs/{org_slug}/apps/{app_uuid}/env`: List an app's environment variables without their values (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-get-api-v1-orgs-org-slug-apps-app-uuid-env) - `PUT /api/v1/orgs/{org_slug}/apps/{app_uuid}/env`: Set many variables; a variable given without value keeps its saved one (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-put-api-v1-orgs-org-slug-apps-app-uuid-env) - `PUT /api/v1/orgs/{org_slug}/apps/{app_uuid}/env/{key}`: Set one environment variable, creating it when it is new (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-put-api-v1-orgs-org-slug-apps-app-uuid-env-key) - `DELETE /api/v1/orgs/{org_slug}/apps/{app_uuid}/env/{key}`: Delete one environment variable (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-delete-api-v1-orgs-org-slug-apps-app-uuid-env-key) # Add a custom domain > Serve an app on your own domain once you prove it is yours with a TXT record and point it at the app. Source: https://www.coritan.com/docs/apps/custom-domains/ In the dashboard: - /dashboard/apps/…/domains: https://www.coritan.com/dashboard/apps Every app answers at its platform name. Add a custom domain, such as `www.example.com`, to serve the app on your own name as well. You prove the domain is yours with a TXT record, point it at the app with a CNAME record, and we issue its certificate. The app's **Domains** tab lists its domains and gives the records to publish. ## Before you begin - You need a domain whose DNS records you can change at your DNS host. If Coritan hosts its DNS, see [Add, edit and delete DNS records](/docs/websites/dns/manage-dns-records/). - For an organization's app, you need the owner or admin role in the organization. - An app can have up to 20 custom domains. The **Domains** tab says how many it uses, such as `1 of 20 custom domains in use.` ## The platform name When you create an app, we give it a platform name made of its name on the platform and the platform's app domain, such as `web-shop.apps.example.net`. It is verified from the start, it goes with the app, and you cannot remove it. The **Domains** tab lists it first, as `Platform address`. Names under the app domain are only given out this way, so you cannot add one as a custom domain. When the platform has no app domain, apps get no platform name and serve only their custom domains. ## Add and verify a domain 1. Open the app from **Apps** in the [dashboard](https://www.coritan.com/dashboard/apps) and select the **Domains** tab. 2. Select **Add domain…**. In **Domain**, type the hostname, such as `www.example.com` or `example.com`, without `https://` or a path. Wildcard names such as `*.example.com` are not accepted. Select **Add domain**. A message confirms `www.example.com added.`, and the domain's row shows **Pending verification**. 3. Under the row, **Prove the domain is yours** gives the TXT record. Publish it at your DNS host: ```dns _coritan-app.www.example.com. 3600 IN TXT "8b7c1d0e2f3a4b5c6d7e8f9012345678" ``` The name is `_coritan-app.` followed by your hostname, and the value is the token we gave the domain. Each has a copy button. 4. Under that, **Send its visitors to the app** gives the CNAME record that points the hostname at the app. Its **Target** is the app's platform name: ```dns www.example.com. 3600 IN CNAME web-shop.apps.example.net. ``` At the top of a zone, such as `example.com`, a CNAME is not allowed. Use your DNS host's ALIAS record or CNAME flattening to the same target. 5. Select **Verify** on the domain's row. When we find the TXT record, a message confirms `www.example.com verified.`, and the domain starts serving the app. When we do not, `Not verified yet` under the row says which record we looked for. Wait a few minutes for DNS to update, then select **Verify** again. The domain gets no route until it is verified, so nothing reaches the app on it before then. You can remove the TXT record once the domain is verified. ## Read the domain list **Domain** : The hostname, and whether it is the `Platform address` or a `Custom domain`. A verified domain's name opens the app in a new tab, and a verified custom domain also shows its CNAME target. **Status** : **Verified** with its certificate, or **Pending verification** with `Waiting for its TXT record`. **Added** : When the domain was added. The menu at the end of each row has **Open in a new tab** for a verified domain, **Copy address**, and **Remove domain…** for a custom domain. ## Certificates A verified domain's certificate is ordered once the app has replicas serving it. It moves from `No certificate yet` to `Certificate on its way` to `Certificate issued`, which the API sends as `none`, `pending` and `issued`. The certificate authority checks the domain over HTTP, so the CNAME record from step 4 must be in place for the certificate to be issued. ## A hostname in use elsewhere A hostname can belong to one app at a time. Another app that is still verifying a hostname holds it for 72 hours after adding it. After that, the next app that adds the hostname takes it over. A hostname that another app has verified, or that another product on the platform serves, such as a [web proxy](/docs/proxies/web-proxies/), cannot be added. ## Remove a domain Removing a custom domain stops it reaching the app at once, and deletes its route and certificate. The records at your DNS host stay until you delete them. The platform name cannot be removed. 1. On the **Domains** tab, open the menu at the end of the domain's row and select **Remove domain…**. 2. Select **Remove domain** to confirm. A message confirms `www.example.com removed.` ## Result The domain shows **Verified**, and `Certificate issued` once the certificate authority has checked it. Visitors reach the app at `https://www.example.com`. ## Troubleshooting `No TXT record at _coritan-app.www.example.com holds the token yet. DNS changes can take a few minutes to appear.` : The record is not published yet, or holds another value. Check its name and value at your DNS host, wait a few minutes, then check again. `That hostname is in use on the platform` : Another app has verified it, or added it less than 72 hours ago. If it is yours, remove it from the other app first. `That hostname is already served by another route on the platform` : Another product serves it, such as a web proxy. Remove it there first. `Names under apps.example.net are given to apps by the platform` : The hostname is under the platform's app domain. Each app gets its own name there when it is created. `An app can have at most 20 custom domains` : Remove a domain the app no longer needs. While the app has 20, **Add domain…** is turned off. The certificate stays `Certificate on its way` : The hostname does not point at the app yet. Check the CNAME record, or the ALIAS record at the top of a zone. `Too many checks for this app. Wait a few minutes; DNS changes take a while anyway.` : The app asked for 30 checks in the last 10 minutes. Wait, then check again. ## Related - [How Apps works](/docs/apps/how-apps-work/#going-live) - [Add, edit and delete DNS records](/docs/websites/dns/manage-dns-records/) - [Apps limits](/docs/apps/limits/) ## With the API The hostname in a path is the domain's `hostname`, such as `www.example.com`. For an organization's app, send the same requests under `/api/v1/orgs/{org_slug}/apps` ([organization apps reference](/docs/api/reference/organizations/organization-apps/#op-get-api-v1-orgs-org-slug-apps-app-uuid-domains)). ### List domains [`GET /api/v1/client/apps/{app_uuid}/domains`](/docs/api/reference/client/apps/#op-get-api-v1-client-apps-app-uuid-domains) lists the platform name first, then the custom domains: ```bash curl https://api.coritan.com/api/v1/client/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21/domains \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "domains": [ { "hostname": "web-shop.apps.example.net", "kind": "platform", "verified": true, "verified_at": "2026-09-20T09:12:00Z", "created_at": "2026-09-20T09:12:00Z", "url": "https://web-shop.apps.example.net", "certificate": "issued", "verification": null, "cname_target": null }, { "hostname": "www.example.com", "kind": "custom", "verified": false, "verified_at": null, "created_at": "2026-09-26T10:05:00Z", "url": null, "certificate": "none", "verification": {"type": "TXT", "name": "_coritan-app.www.example.com", "value": "8b7c1d0e2f3a4b5c6d7e8f9012345678"}, "cname_target": "web-shop.apps.example.net" } ] } ``` `kind` is `platform` or `custom`. `url` is `null` until the domain is verified. `certificate` is `none`, `pending` or `issued`. `verification` is the TXT record to publish, and `null` once the domain is verified. `cname_target` is where a custom domain points, and `null` for the platform name. ### Add a domain through the API [`POST /api/v1/client/apps/{app_uuid}/domains`](/docs/api/reference/client/apps/#op-post-api-v1-client-apps-app-uuid-domains) adds a custom domain and answers `201` with it, in the shape above: ```bash curl -X POST https://api.coritan.com/api/v1/client/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21/domains \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"hostname": "www.example.com"}' ``` | Status | `detail` | Cause | | --- | --- | --- | | `409` | `{"error": "domain_exists", "message": "That hostname is already added to this app"}` | The app has it already. | | `409` | `{"error": "hostname_in_use", "message": "That hostname is in use on the platform"}` | Another app holds it, or another product serves it. | | `409` | `{"error": "domain_limit", "message": "An app can have at most 20 custom domains"}` | The app has 20 custom domains. | | `422` | `{"message": "Give the hostname alone, without http:// or a path", "field": "hostname"}` | `hostname` starts with `http://` or `https://`. | | `422` | `{"message": "'*.example.com' is not a hostname", "field": "hostname"}` | `hostname` is not a hostname. | | `422` | `{"message": "Names under apps.example.net are given to apps by the platform", "field": "hostname"}` | The name is under the app domain. | ### Verify a domain through the API [`POST /api/v1/client/apps/{app_uuid}/domains/{hostname}/verify`](/docs/api/reference/client/apps/#op-post-api-v1-client-apps-app-uuid-domains-hostname-verify) looks for the TXT record: ```bash curl -X POST https://api.coritan.com/api/v1/client/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21/domains/www.example.com/verify \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` When it finds the record, it answers `{"domain": {...}, "verified": true, "certificates": ["www.example.com"]}`, where `certificates` names the hostnames whose certificate it ordered. When it does not, it answers `200` with `"verified": false` and a `message` saying which record it looked for. A domain that is already verified answers `"verified": true` with an empty `certificates`. It answers `409` with `hostname_in_use` when another product took the hostname meanwhile, and `429` after 30 checks for the app in 10 minutes. ### Remove a domain through the API [`DELETE /api/v1/client/apps/{app_uuid}/domains/{hostname}`](/docs/api/reference/client/apps/#op-delete-api-v1-client-apps-app-uuid-domains-hostname) removes a custom domain and answers `{"deleted": "www.example.com"}`. The platform name answers `409` with `{"error": "platform_domain", "message": "The platform name goes with the app and cannot be removed"}`, and a hostname the app does not have answers `404` with `Domain not found`. ## API - `GET /api/v1/client/apps/{app_uuid}/domains`: List an app's domains: its platform name first, then its custom domains (https://www.coritan.com/docs/api/reference/client/apps/#op-get-api-v1-client-apps-app-uuid-domains) - `POST /api/v1/client/apps/{app_uuid}/domains`: Add a custom domain (https://www.coritan.com/docs/api/reference/client/apps/#op-post-api-v1-client-apps-app-uuid-domains) - `DELETE /api/v1/client/apps/{app_uuid}/domains/{hostname}`: Remove a custom domain from an app (https://www.coritan.com/docs/api/reference/client/apps/#op-delete-api-v1-client-apps-app-uuid-domains-hostname) - `POST /api/v1/client/apps/{app_uuid}/domains/{hostname}/verify`: Look for the domain's TXT record (https://www.coritan.com/docs/api/reference/client/apps/#op-post-api-v1-client-apps-app-uuid-domains-hostname-verify) - `GET /api/v1/orgs/{org_slug}/apps/{app_uuid}/domains`: List an app's domains: its platform name first, then its custom domains (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-get-api-v1-orgs-org-slug-apps-app-uuid-domains) - `POST /api/v1/orgs/{org_slug}/apps/{app_uuid}/domains`: Add a custom domain (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-post-api-v1-orgs-org-slug-apps-app-uuid-domains) - `DELETE /api/v1/orgs/{org_slug}/apps/{app_uuid}/domains/{hostname}`: Remove a custom domain from an app (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-delete-api-v1-orgs-org-slug-apps-app-uuid-domains-hostname) - `POST /api/v1/orgs/{org_slug}/apps/{app_uuid}/domains/{hostname}/verify`: Look for the domain's TXT record (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-post-api-v1-orgs-org-slug-apps-app-uuid-domains-hostname-verify) # Deploy on every push > Add an app's push webhook to your git host so that each push to the app's branch builds and deploys that commit. Source: https://www.coritan.com/docs/apps/push-webhook/ In the dashboard: - /dashboard/apps/…/settings: https://www.coritan.com/dashboard/apps Every app that builds from git has a push webhook. Add its address and secret to the repository at your git host, and each push to the app's branch starts a deployment of the pushed commit. It works with GitHub, GitLab, Gitea, Forgejo and Gogs. The **Push webhook** card on the app's **Settings** tab shows the address and makes new secrets. ## Before you begin - The app must build from git. An app that runs an image has no push webhook. - You need the webhook's secret. It appears once, when you create the app, in the **Push webhook secret** dialog. If you did not save it, [make a new secret](#make-a-new-secret). - You need permission to add webhooks to the repository at your git host. - For an organization's app, you need the owner or admin role in the organization to make a new secret. ## Add the webhook at your git host 1. Find the webhook's address. Open the app from **Apps** in the [dashboard](https://www.coritan.com/dashboard/apps), select the **Settings** tab, and copy the **Payload URL** from the **Push webhook** card. Through the API, it is the app's `webhook.url`, such as `https://api.coritan.com/api/v1/apps/hooks/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21`. 2. At your git host, open the repository's webhook settings and add a webhook with that address. 3. Where the host asks for a content type, choose `application/json`. GitHub's form encoded deliveries work too. 4. Paste the app's webhook secret into the webhook's secret field. GitLab calls it the secret token. 5. Choose push events. The webhook ignores every other event, so choosing more only adds deliveries that do nothing. 6. Save the webhook. GitHub then sends a test delivery called a ping, which the webhook answers with `200`, so GitHub shows the webhook as working. The address takes no access token. The secret proves that a delivery comes from your git host. The **Push webhook** card also shows what to choose at your git host: **Content type** `application/json` and **Events** `Push`. Its **Secret** row reads `Set, ends in f0a9` with the last four characters of the secret, or `Not set`. While no secret is set, the card says `Pushes are refused until the webhook has a secret`. ## What a push deploys A delivery starts a deployment when all of these hold: - It is a push to a branch, and that branch is the app's branch. A push to another branch, a tag or a deleted branch deploys nothing. - It comes from the app's own repository. When the delivery names its repository, we compare the host and path with the app's repository address. - It names the commit at the head of the push. - The app is `active`. The deployment builds the pushed commit and records its message and author. Its `trigger` is `push` and its `actor_type` is `webhook`, so you can tell it apart from one you started. When several pushes arrive close together, each starts a deployment, and the newest to go live cancels any older one still on its way ([Deployments](/docs/apps/how-apps-work/#deployments)). A delivery deploys at most once. We remember each delivery's ID for a day, so a redelivery from your git host is ignored. A push of the commit that the app's latest push deployment already has is ignored too. To run the same commit again, [redeploy](/docs/apps/deploy-an-app/#redeploy) the app. An app can deploy 30 pushes in 5 minutes. Past that, the webhook answers `429` until the 5 minutes have passed. ## How a delivery is checked We check the delivery against the app's secret before we read it. The header depends on the git host: | Git host | Header | What it holds | | --- | --- | --- | | GitHub | `X-Hub-Signature-256` | `sha256=` and the HMAC SHA-256 of the body, keyed with the secret | | Gitea, Forgejo, Gogs | `X-Gitea-Signature`, `X-Forgejo-Signature` or `X-Gogs-Signature` | The HMAC SHA-256 of the body, keyed with the secret | | GitLab | `X-Gitlab-Token` | The secret itself | We read the delivery's ID from the host's delivery header, such as `X-GitHub-Delivery` or `X-Gitlab-Event-UUID`. A delivery can be up to 10 MB. ## Make a new secret Make a new secret when you have lost the old one, or when someone who should not have it may have seen it. The new secret appears once, and the old one stops working at once. Paste the new secret into your git host's webhook straight away, or pushes answer `401` and deploy nothing. 1. On the app's **Settings** tab, select **Rotate secret…** on the **Push webhook** card. When the app has no secret, the button is **Create secret…**. 2. Select **Rotate secret**, or **Create secret**, to confirm. 3. The **New webhook secret** dialog shows the **Payload URL** and the **Secret**. Copy both, or select **Copy all**, then select **I have saved it**. A message confirms `Webhook secret rotated.` or `Webhook secret created.` The **Secret** row, and the app's `webhook.secret_hint` in the API, show the last four characters of the secret, so you can check which secret your git host holds. ## Result Each push to the app's branch appears as a new deployment in the app's history, with the commit's ID, message and author. On the **Deployments** tab, its source reads `Push to main` and `The push webhook`, and through the API its `trigger` is `push`. Your git host's delivery log shows each answer: `201` for a push that started a deployment, `202` for a delivery the webhook ignored and why, and `200` for a ping. ## Troubleshooting Your git host's delivery log shows the webhook's answer to each delivery. A `202` answer names what the webhook ignored in `ignored`: `push to feature; the app deploys main` : The push was to another branch. Push to the app's branch, or change the branch the app deploys ([Change the settings](/docs/apps/app-settings/#change-the-settings)). `tag push` : Tags do not deploy on their own. GitLab's tag pushes show as `not a push: tag_push`. Deploy the tag yourself ([Choose what to deploy](/docs/apps/deploy-an-app/#choose-what-to-deploy)). `push from another repository` : The delivery came from a repository other than the app's, such as a fork, or the repository moved. Correct the app's repository address. `the app is suspended` : We suspended the app. [Contact support](/docs/support/conversations/) to find out why. `this delivery was already received` or `the latest push deployment already has this commit` : The push was deployed already. Redeploy the app to run it again. `not a push: issues` : The webhook sends other events. Choose push events only. The webhook refuses a delivery with one of these, in `detail`: `Sign the delivery with the app's webhook secret.` : The delivery has no signature. Paste the secret into the webhook at your git host. `The signature does not match the app's webhook secret.` : Your git host holds another secret, often one from before you made a new one. Paste the current secret, or make a new one. `This app has no webhook secret. Create one in its settings.` : Select **Create secret…** on the **Push webhook** card ([Make a new secret](#make-a-new-secret)), then paste the secret into your git host. `No git app with this id.` : The address does not belong to an app that builds from git. The app may have been deleted. Copy the **Payload URL** from the app's **Settings** tab again. `The delivery is not JSON.` : Set the webhook's content type to `application/json`. `Too many pushes to deploy. Wait a few minutes.` : The app deployed 30 pushes in the last 5 minutes. Later pushes deploy again once the 5 minutes have passed. `Builds are not available yet on this platform.` : The platform cannot build apps at the moment. Try again later, or [contact support](/docs/support/conversations/). ## Related - [Deploy an app](/docs/apps/deploy-an-app/) - [Read a deployment and its build log](/docs/apps/read-a-deployment/) - [Change or delete an app](/docs/apps/app-settings/) ## With the API ### Make a new secret through the API [`POST /api/v1/client/apps/{app_uuid}/webhook/rotate`](/docs/api/reference/client/apps/#op-post-api-v1-client-apps-app-uuid-webhook-rotate) makes a new secret. For an organization's app, send it to [`POST /api/v1/orgs/{org_slug}/apps/{app_uuid}/webhook/rotate`](/docs/api/reference/organizations/organization-apps/#op-post-api-v1-orgs-org-slug-apps-app-uuid-webhook-rotate). ```bash curl -X POST https://api.coritan.com/api/v1/client/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21/webhook/rotate \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "webhook": { "url": "https://api.coritan.com/api/v1/apps/hooks/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21", "secret": "4f1d2c3b5a69788796a5b4c3d2e1f0a94f1d2c3b5a69788796a5b4c3d2e1f0a9" } } ``` `secret` appears in this answer only. An app that runs an image answers `409` with `{"error": "not_git", "message": "Only an app that builds from git has a push webhook"}`. ### The webhook's answers Your git host sends deliveries to `POST /api/v1/apps/hooks/{app_uuid}`, the address in `webhook.url`. It is not in the API reference, because only a git host calls it. A success answers with `ok` set to `true` and the header `Cache-Control: no-store`: ```json {"ok": true, "deployment": "0b6f3d2e-7a41-4c59-8e1d-2f9a6b3c4d5e", "status": "queued"} ``` | Status | Body | Meaning | | --- | --- | --- | | `200` | `{"ok": true, "event": "ping", "app": "", "branch": "main"}` | A ping. Nothing deploys. | | `201` | `{"ok": true, "deployment": "", "status": "queued"}` | The push started this deployment. | | `202` | `{"ok": true, "ignored": "tag push"}` | The delivery deploys nothing, for the reason given. A repeated delivery also has `"duplicate": true`. | A refusal has the error in `detail`, as in `{"detail": {"ok": false, "error": "signature_mismatch", "message": "The signature does not match the app's webhook secret."}}`: | Status | `error` | Cause | | --- | --- | --- | | `400` | `invalid_payload` | The body is not JSON. | | `401` | `signature_missing` | The delivery has no signature header. | | `401` | `signature_mismatch` | The signature does not match the app's secret. | | `403` | `webhook_not_configured` | The app has no secret. | | `404` | `not_found` | No app that builds from git has this ID, or Apps is turned off (`Not found.`). | | `409` | `refused` | The deployment was refused, and `message` says why. | | `413` | `too_large` | The body is larger than 10 MB. | | `422` | `repository_invalid` | The app's repository address cannot be built from, and `message` says why. | | `429` | `rate_limited` | The app deployed 30 pushes in 5 minutes. `Retry-After` gives the wait in seconds. | | `503` | `registry_not_configured` | The platform cannot build apps at the moment. | To deploy from a CI job instead, send a deployment request with an access token ([Deploy through the API](/docs/apps/deploy-an-app/#deploy-through-the-api)). ## API - `POST /api/v1/client/apps/{app_uuid}/webhook/rotate`: A new secret for the push webhook, shown once; the old one stops verifying at once (https://www.coritan.com/docs/api/reference/client/apps/#op-post-api-v1-client-apps-app-uuid-webhook-rotate) - `POST /api/v1/orgs/{org_slug}/apps/{app_uuid}/webhook/rotate`: A new secret for the push webhook, shown once; the old one stops verifying at once (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-post-api-v1-orgs-org-slug-apps-app-uuid-webhook-rotate) # Change or delete an app > Change an app's name, build, size, regions or replicas, see which changes need a new deployment, and delete an app you no longer need. Source: https://www.coritan.com/docs/apps/app-settings/ In the dashboard: - /dashboard/apps/…/settings: https://www.coritan.com/dashboard/apps You can change most of an app's settings after you create it, on the app's **Settings** tab or through the API. Some changes apply at once, and others reach the running replicas only with the next deployment or the next build. The app's source type and its name on the platform stay as you created them. ## Before you begin - For an organization's app, you need the owner or admin role in the organization. - To delete an organization's app from the staff console, you need to have confirmed it is you in the last 10 minutes ([Confirm it is you](/docs/organizations/staff-console/sign-in/#confirm-it-is-you)). ## Change the settings 1. Open the app from **Apps** in the [dashboard](https://www.coritan.com/dashboard/apps) and select the **Settings** tab. 2. Change the fields on one card. Its foot counts what you changed, such as `2 changes to save`, and **Discard** puts the fields back. 3. Select **Save changes** on that card. A message confirms the save, such as `Run settings saved.` Each card saves only its own fields, so save each card you change. The rules for each value are the same as when you create an app ([What you choose](/docs/apps/create-an-app/#what-you-choose)). **General** : **Name**. **Name on the platform** is shown but cannot change. **Source** : For a git app, **Repository**, **Branch**, **Directory** and **Access token**. For an image app, **Image**. **Build and start** : For a git app, **Framework**, **Dockerfile**, **Install command**, **Build command** and **Start command**. **Run** : **Port**, **Health check path** and **Size**. **Regions and replicas** : **Every region** or **The regions I choose**, and **Replicas per region**. **Push webhook** : For a git app, the address and secret that let pushes deploy it ([Deploy on every push](/docs/apps/push-webhook/)). **Delete this app** : Deleting the app ([Delete an app](#delete-an-app)). A few fields work differently: - To replace the access token of a private repository, type the new one in **New access token**. To remove it, tick **Remove the saved token**. We never show a token, and the field's hint shows its last four characters. - To clear the directory, the Dockerfile, the framework or a command, empty its field. - To run the app in every region again, choose **Every region**. Changes to environment variables have their own tab ([Set environment variables](/docs/apps/environment-variables/)), and so do custom domains ([Add a custom domain](/docs/apps/custom-domains/)). ## When a change applies | Setting | When it applies | | --- | --- | | Name | At once. It is a label and changes nothing that runs. | | Health check path | At once. The next health check of every replica uses it. | | Repository and branch | At once for the push webhook, which then deploys only pushes to the new branch and repository. At the next build for the running app. | | Directory, access token, Dockerfile, install, build and start commands | At the next build. | | Image, for an app that runs an image | At the next deployment of the new image. | | Size, regions, replicas per region | At the next deployment. A redeploy is enough. | After a save that needs a new deployment, a note under the app's header says which kind: `A redeploy applies the saved changes`, `A new build applies the saved changes`, or for an image app `The new image is not running yet`. Its **Redeploy…** button starts the redeploy, with **Build the branch again** or **Pull the image again** ticked when the change needs it ([Redeploy](/docs/apps/deploy-an-app/#redeploy)). A plain redeploy of an image app runs the image that is running now, so after changing the image, pull it again or deploy it. A change to the port also asks for a redeploy, and a change to the framework for a new build. Neither setting changes how the app runs: replicas listen on the port in their `PORT` variable ([What your app must do](/docs/apps/how-apps-work/#what-your-app-must-do)), and the build does not read the framework. > [!WARNING] > A new health check path applies to the running replicas at their next check. Change it only when the running version answers on the new path, or its replicas fail their checks and we start to replace them. ## A suspended app We can suspend an app. Suspending it cancels its deployments on their way, stops its domains reaching it and stops its replicas. Its `status` is then `suspended`, and the app's page shows `This app is suspended` with an **Open a ticket** button. While an app is suspended, you can read it, change its settings and delete it. **Deploy…** and **Redeploy…** are turned off, no deployment offers **Roll back to this…**, and the API answers `409` with `The app is suspended; it cannot deploy`. The push webhook ignores pushes. When we resume the app, its current deployment starts again. Open a ticket, or [contact support](/docs/support/conversations/), to find out why an app was suspended. ## Delete an app > [!CAUTION] > Deleting an app cannot be undone. Save anything you need from it, such as its environment variables, first. Deleting an app does all of this: - Cancels its deployments on their way. - Removes its platform name and custom domains, with their routes and certificates, so visitors can no longer reach it. - Stops its replicas and removes them. - Erases its environment variables, its access token and its push webhook secret. - Frees its name on the platform, so another app can take it. To delete it in the dashboard: 1. On the app's **Settings** tab, go to **Delete this app** at the bottom and select **Delete app…**. 2. Type the app's name on the platform, as the dialog shows it, such as `web-shop`. Case and spaces around it do not matter. 3. Select **Delete app**. A message confirms `Web shop deleted.`, and the **Apps** list opens. Through the API, you can also give a reason of up to 500 characters, which goes into the audit log with the deletion. For an organization's app, only an owner or admin can delete it. From a staff console session, they also need to have confirmed it is them in the last 10 minutes. A request made with the member's own Coritan access token needs only the typed name. ## Result After a save, the card shows the new settings. Settings that need a new deployment or build apply once you start one. After you delete an app, it no longer appears in your apps, and a request for it answers `404` with `App not found`. Its domains stop reaching it at once. ## Troubleshooting `Could not save the changes` : We refused the change, and the reason is under this heading or under the field. The messages below are the common ones. `SYD takes no new replicas` : A region the app asks for no longer takes new replicas, so the next deployment fails. Untick it, or choose **Every region**, and save. `Type the app's name, web-shop, to confirm` : The name you typed is not the app's name on the platform. Type it as the message shows it. `Confirm your password or a code from your authenticator to do this.` : The staff console session has not confirmed it is you in the last 10 minutes. Confirm it, then delete the app again. `Admin access required` : Only an owner or admin of the organization can change or delete its apps. `image_ref is for apps that run an image; this app builds from git` : A git app cannot switch to an image, or back. Create a new app with the other source. `'release 2' is not a branch name git accepts` : A branch name cannot have spaces and some other characters. Check the branch's name at your git host. `These regions cannot run apps: syd` : A region you chose cannot run apps now. Choose from the regions the platform offers ([Regions, sizes and limits](/docs/apps/#regions-sizes-and-limits)). `min_replicas must be between 1 and 5` : Choose from 1 to 5 replicas in each region. `Too many changes to apps. Wait a while and try again.` : You made 120 changes to apps in the last hour. Wait, then try again ([Requests](/docs/apps/limits/#requests)). ## Related - [Deploy an app](/docs/apps/deploy-an-app/#redeploy) - [Set environment variables](/docs/apps/environment-variables/) - [App and deployment fields](/docs/apps/fields/) - [Organization roles and permissions](/docs/organizations/roles-and-permissions/) ## With the API ### Change settings through the API [`PATCH /api/v1/client/apps/{app_uuid}`](/docs/api/reference/client/apps/#op-patch-api-v1-client-apps-app-uuid) changes an app's settings. For an organization's app, send it to [`PATCH /api/v1/orgs/{org_slug}/apps/{app_uuid}`](/docs/api/reference/organizations/organization-apps/#op-patch-api-v1-orgs-org-slug-apps-app-uuid). Send only the settings you change. ```bash curl -X PATCH https://api.coritan.com/api/v1/client/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21 \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"repo_branch": "production", "instance_size": "medium", "min_replicas": 3}' ``` The body takes the fields of [the create request](/docs/apps/create-an-app/#with-the-api) except `slug`, `source_type`, `env` and `deploy`. Sending one of those answers `422` with a field error whose `type` is `extra_forbidden` ([A list of field errors](/docs/api/errors/#a-list-of-field-errors)). - A new `repo_token` replaces the token, and an empty one removes it. The app's `repo_token_hint` shows the last four characters of the token it holds. - An empty `repo_subdir`, `dockerfile_path`, `framework`, `install_command`, `build_command` or `start_command` clears it. - An empty `health_check_path` sets it to `/`. - `["*"]` or `null` as `regions` sets them to every region. - A `max_replicas` below the new `min_replicas` is raised to it. It answers with the app and what changed: ```json { "app": { "uuid": "3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21", "repo_branch": "production", "instance_size": "medium", "memory_mb": 1024, "cpu_percent": 200, "disk_mb": 4096, "min_replicas": 3, "max_replicas": 3 }, "changed": ["cpu_percent", "disk_mb", "instance_size", "max_replicas", "memory_mb", "min_replicas", "repo_branch"], "redeploy_required": true, "rebuild_required": true } ``` The example shortens `app`, which carries every field ([App and deployment fields](/docs/apps/fields/)). `changed` lists the fields whose value changed, and names a new or removed token as `repo_token`. A size sets `memory_mb`, `cpu_percent` and `disk_mb` with it. `rebuild_required` is `true` when the change reaches the app only through a new build, so [redeploy with a rebuild](/docs/apps/deploy-an-app/#redeploy) or push a commit. `redeploy_required` is `true` when a new deployment of the current image is enough, so a plain redeploy applies it. After changing an app's image, redeploy with a rebuild or deploy the image, because a plain redeploy runs the image that is running now. `redeploy_required` is also `true` after a change to the port, and `rebuild_required` after a change to the framework. | Status | `detail` | Cause | | --- | --- | --- | | `404` | `App not found` | The app belongs to someone else, or does not exist. | | `422` | `repo_url is for apps that build from git` | An app that runs an image was sent a repository field. | | `422` | `image_ref is for apps that run an image; this app builds from git` | A git app was sent `image_ref`. | | `422` | `{"message": "These regions cannot run apps: syd", "unavailable": ["syd"], "available": ["fra", "iad"]}` | A region in `regions` cannot run apps now. | | `422` | `min_replicas must be between 1 and 5` | `min_replicas` is out of range. | | `429` | `{"error": "rate_limited", "message": "Too many changes to apps. Wait a while and try again.", "retry_after_seconds": 3600}` | You made 120 changes to apps in the last hour. | ### Delete an app through the API [`DELETE /api/v1/client/apps/{app_uuid}`](/docs/api/reference/client/apps/#op-delete-api-v1-client-apps-app-uuid) deletes an app. Give the app's name on the platform as `confirm`, and a reason as `reason` if you like. For an organization's app, send it to [`DELETE /api/v1/orgs/{org_slug}/apps/{app_uuid}`](/docs/api/reference/organizations/organization-apps/#op-delete-api-v1-orgs-org-slug-apps-app-uuid). ```bash curl -X DELETE "https://api.coritan.com/api/v1/client/apps/3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21?confirm=web-shop&reason=Moved%20to%20a%20new%20app" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "deleted": true, "uuid": "3c9e2f4a-8b1d-4f6e-a2c7-5d8b9e0f1a21", "slug": "web-shop", "routes_removed": ["web-shop.apps.example.net", "www.example.com"] } ``` `routes_removed` lists the hostnames that stopped reaching the app. | Status | `detail` | Cause | | --- | --- | --- | | `403` | `Admin access required` | The member is not an owner or admin of the organization. | | `403` | `{"error": "reauth_required", "message": "Confirm your password or a code from your authenticator to do this.", "max_age_seconds": 600}` | A staff console session has not confirmed it is you in the last 10 minutes. | | `404` | `App not found` | The app belongs to someone else, does not exist, or is deleted already. | | `422` | `{"error": "confirm_mismatch", "message": "Type the app's name, web-shop, to confirm"}` | `confirm` is not the app's name on the platform. | | `429` | `{"error": "rate_limited", "message": "Too many changes to apps. Wait a while and try again.", "retry_after_seconds": 3600}` | You made 120 changes to apps in the last hour. | ## API - `PATCH /api/v1/client/apps/{app_uuid}`: Change an app's settings (https://www.coritan.com/docs/api/reference/client/apps/#op-patch-api-v1-client-apps-app-uuid) - `DELETE /api/v1/client/apps/{app_uuid}`: Delete app (https://www.coritan.com/docs/api/reference/client/apps/#op-delete-api-v1-client-apps-app-uuid) - `PATCH /api/v1/orgs/{org_slug}/apps/{app_uuid}`: Change an app's settings (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-patch-api-v1-orgs-org-slug-apps-app-uuid) - `DELETE /api/v1/orgs/{org_slug}/apps/{app_uuid}`: Delete app (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-delete-api-v1-orgs-org-slug-apps-app-uuid) # Run apps for an organization > Create and run apps that belong to an organization from its Apps tab, and see which members can change them. Source: https://www.coritan.com/docs/apps/organization-apps/ In the dashboard: - /dashboard/organizations/…/apps: https://www.coritan.com/dashboard/organizations An organization can own apps, apart from the apps on your account. Its **Apps** tab lists them and opens the same pages your own apps have, for deployments, domains, environment variables and settings. Owners and admins make changes there. Every other member of the organization sees the same pages without the controls that change anything. ## Before you begin - Apps must be on for the platform. While it is off, the organization has no **Apps** tab, and an address under the tab opens the organization's **Overview** instead. - You need to be a member of the organization. To create, deploy, change or delete its apps, you need the owner or admin role ([Organization roles and permissions](/docs/organizations/roles-and-permissions/)). ## Open the organization's apps 1. In the [dashboard](https://www.coritan.com/dashboard/organizations), go to **Organizations** and open the organization. 2. Select the **Apps** tab, after **Cloud Compute**. 3. Select an app to open its page. **All apps** takes you back to the list. The **Apps** card lists each app with its **Status**, **Regions** and **Last deployment**, as your own list does ([Find your apps in the dashboard](/docs/apps/#find-your-apps-in-the-dashboard)). Search by name, address, repository or image, and filter the list to **All**, **Live**, **Not live** or **Suspended** apps. The card's description counts the organization's apps against its limit, such as `The organization's apps: 3 of 20 apps used.` An app's page has the tabs your own apps have, and each one works as its page describes: - **Deployments**: [Deploy an app](/docs/apps/deploy-an-app/), [Read a deployment and its build log](/docs/apps/read-a-deployment/) and [Roll back to an earlier deployment](/docs/apps/roll-back-a-deployment/). - **Domains**: [Add a custom domain](/docs/apps/custom-domains/). - **Environment**: [Set environment variables](/docs/apps/environment-variables/). - **Settings**: [Change or delete an app](/docs/apps/app-settings/) and [Deploy on every push](/docs/apps/push-webhook/). ## Create an app for the organization 1. On the organization's **Apps** tab, select **New app…**. 2. Fill in the form as for an app of your own ([Create an app](/docs/apps/create-an-app/#create-an-app-in-the-dashboard)). 3. Select **Create app**. The app belongs to the organization, not to the member who created it. When the organization has as many apps as it may, **New app…** is off and the card's description ends `Delete an app to create another.` ## What members below admin see With a role below admin (Billing, any support tier or Read only), you can open every app and read its deployments, build logs, domains, variable names and settings. The top of the tab says `Changes here need an owner or admin; the tables below are read-only for you.` Your pages have no **New app…**, **Deploy…** or **Redeploy…** button, and: - A deployment has no **Roll back to this…** or **Cancel deployment…**, in its menu or on its page. - **Domains** has no **Add domain…**, **Verify** or **Remove domain…**. - **Environment** has no **Add variable…**, and no menu for each variable. - **Settings** has no save buttons, no **Rotate secret…** or **Create secret…** and no **Delete this app** card, and its fields cannot be edited. The **Access token** field shows `None`, or `Saved` with the token's last characters. The address of the create form, `…/apps/new`, shows you `Creating an app needs an owner or admin`. To make changes, ask the owner or an admin to give you the admin role ([Change a member's role](/docs/organizations/members-and-roles/#change-a-member-s-role)). ## How an organization's apps differ from yours An organization's apps work as your own do, with these differences: - Only the organization's **Apps** tab lists them. **Apps** in the sidebar lists the apps on your account, and never an organization's, even one you own. - Every member of the organization can see them. Only owners and admins can change them. - They count towards the organization's app limit, which is separate from your account's ([Apps limits](/docs/apps/limits/#apps)). - In the deployment history, a deployment that a member started says `A member` where yours say `You`. One started from a staff console session says `The staff console`. - Each change a member makes goes into the organization's audit log, on the **Audit** tab for owners and admins ([Read the organization audit log](/docs/organizations/audit-log/)). Its actions start with `app.`, such as `app.deploy`, and the **Actor** column names the member. Deployments that a push starts are not in the log; the deployment history shows them, started by `The push webhook`. - An app stays with the owner it was created for. No request moves it between your account and an organization. - The organization cannot be deleted while it has apps. The **Delete organization** card on its **Settings** tab lists them as, for example, `2 apps still running`, with `Delete them first; their replicas and domains go with them.` Delete each one from its own **Settings** tab ([Delete an app](/docs/apps/app-settings/#delete-an-app)). ## Result The organization's apps are on its **Apps** tab, and every member of the organization can open them. Owners and admins deploy and change them there, and the other members read them. ## Troubleshooting The organization has no **Apps** tab : Apps is off for the platform, or the dashboard could not reach Apps when the page loaded. Reload the page to ask again. When **Apps** is missing from the sidebar too, see [Apps is not in the dashboard](/docs/apps/troubleshooting/#apps-is-not-in-the-dashboard). A button such as **New app…** or **Deploy…** is missing : Your role in the organization is below admin. The badge next to the organization's name shows your role. [A button is missing or turned off](/docs/apps/troubleshooting/#a-button-is-missing-or-turned-off) lists the other reasons. `This app is not in this organization` : The app belongs to another organization or to an account, it was deleted, or the address is mistyped. Open it from the organization's **Apps** tab. An app of your own is under **Apps** in the sidebar. `Could not load the organization's apps` : The list did not load. Select **Try again**. `The organization has as many apps as it may` : The organization has reached its app limit. Delete an app it no longer needs, then create the new one. `No such organization` : You are not a member of the organization, or it does not exist. Ask its owner to add you ([Add a member](/docs/organizations/members-and-roles/#add-a-member)). ## Related - [Apps](/docs/apps/) - [Create an app](/docs/apps/create-an-app/) - [Change or delete an app](/docs/apps/app-settings/) - [Organization roles and permissions](/docs/organizations/roles-and-permissions/) - [Manage members and roles](/docs/organizations/members-and-roles/) - [Read the organization audit log](/docs/organizations/audit-log/) - [Delete an organization](/docs/organizations/delete-an-organization/) ## With the API An organization's apps are under `/api/v1/orgs/{org_slug}/apps`. The requests take the same bodies and give the same answers as your own apps under `/api/v1/client/apps` ([Apps for an organization](/docs/apps/#apps-for-an-organization)), and each task page shows its request with the organization's path. [`GET /api/v1/orgs/{org_slug}/apps`](/docs/api/reference/organizations/organization-apps/#op-get-api-v1-orgs-org-slug-apps) lists the organization's apps: ```bash curl "https://api.coritan.com/api/v1/orgs/acme/apps" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` Each app in the answer has `owner` set to `{"type": "organization"}`. In the answer to `GET /api/v1/orgs/{org_slug}/apps/regions`, `limits.apps_used` counts the organization's apps, not yours ([Regions, sizes and limits](/docs/apps/#regions-sizes-and-limits)). Any member's access token can read, and a request that changes something needs the owner or admin role. These answers are particular to the organization's paths: | Status | Message | Why | | --- | --- | --- | | `403` | `Admin access required` | The member is not an owner or admin of the organization. | | `403` | `Not a member of this organization` | The Coritan account is not a member of the organization. | | `403` | `Confirm your password or a code from your authenticator to do this.` | A staff console session asked to delete an app without a step-up in the last 10 minutes ([Confirm it is you](/docs/organizations/staff-console/sign-in/#confirm-it-is-you)). | | `404` | `Organization not found` | No active organization has that slug. | ## API - `GET /api/v1/orgs/{org_slug}/apps`: List apps (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-get-api-v1-orgs-org-slug-apps) - `GET /api/v1/orgs/{org_slug}/apps/regions`: Regions (https://www.coritan.com/docs/api/reference/organizations/organization-apps/#op-get-api-v1-orgs-org-slug-apps-regions) # App and deployment fields > Every field the Apps API returns for an app, a deployment, a build, a replica, a domain and an environment variable. Source: https://www.coritan.com/docs/apps/fields/ This page lists the fields of each object the Apps API returns. Times are in UTC, written as in `2026-09-26T10:05:00Z`, and a field with nothing to show is `null`. ## App | Field | Meaning | | --- | --- | | `uuid` | The app's ID, used in every path. | | `name` | What you call the app. | | `slug` | Its name on the platform, which its platform name is made from. | | `status` | `active` or `suspended` ([A suspended app](/docs/apps/app-settings/#a-suspended-app)). | | `owner` | `{"type": "user"}` for your own app, `{"type": "organization"}` for an organization's. | | `source_type` | `git` or `image`. | | `repo_url`, `repo_branch`, `repo_subdir` | A git app's repository address, branch and directory. `repo_subdir` is `null` for the root of the repository. | | `repo_token_set` | `true` when the app holds an access token for its repository. | | `repo_token_hint` | The token's last four characters, or `null` when there is none or it is shorter than eight characters. | | `image_ref` | The image an app that runs an image deploys. | | `framework`, `install_command`, `build_command`, `start_command`, `dockerfile_path` | How a git app builds and starts ([Builds](/docs/apps/how-apps-work/#builds)). | | `port` | The port saved with the app. Replicas listen on the port in their `PORT` variable. | | `health_check_path` | The path each health check requests. | | `instance_size` | `small`, `medium` or `large`. | | `memory_mb`, `cpu_percent`, `disk_mb` | What each replica gets from its size. `cpu_percent` is 100 for each vCPU. | | `regions` | `["*"]` for every region that can run apps, or the region codes the app runs in. | | `min_replicas` | The replicas that run in each region. | | `max_replicas` | Saved with the app. The number of replicas does not grow above `min_replicas`. | | `url` | `https://` and the platform name, or `null` when the app has none. | | `platform_hostname` | The platform name, such as `web-shop.apps.example.net`. | | `domains_count` | How many domains the app has, its platform name included. | | `current_deployment` | The deployment that serves the app, without its list of replicas, or `null` until one has gone live. | | `replicas` | Counts for the current deployment's replicas ([Replica counts](#replica-counts)). | | `webhook` | For a git app, the push webhook: its `url`, `secret_set`, and `secret_hint` with the secret's last four characters. `null` for an app that runs an image. | | `created_at`, `updated_at` | When the app was created and last changed. | ## Deployment | Field | Meaning | | --- | --- | | `uuid` | The deployment's ID. | | `status` | Where it is ([Status words](#status-words)). | | `source` | `git`, `image`, `redeploy` or `rollback`. | | `trigger` | What started it: `manual`, `push` or `rollback`. | | `actor_type` | Who started it: `user`, `staff`, `admin` or `webhook` ([Read a deployment through the API](/docs/apps/read-a-deployment/#read-a-deployment-through-the-api)). | | `git_ref` | The branch or ref a git deployment fetches. | | `git_sha` | The commit it builds. For a deployment of a branch, it is filled in when the build succeeds. | | `commit_message`, `commit_author` | The commit's message and author, for a deployment that a push started and for a redeploy or rollback of one. | | `image_ref` | The image the deployment runs. For a git deployment, it is filled in once the build has pushed the image. | | `image_digest` | The image's digest, once known. | | `regions` | The regions its replicas run in, filled in once it starts deploying. | | `error` | Why it failed or was cancelled, or which regions it went live without. | | `is_current` | `true` for the deployment that serves the app. | | `in_flight` | `true` while it is `queued`, `building` or `deploying`. | | `can_rollback` | `true` when you can roll back to it: it went live once, has an image, and is not the current deployment. | | `replicas` | Counts for its replicas ([Replica counts](#replica-counts)). | | `build` | Its build ([Build](#build)), or `null` for a deployment with no build: an image deployment, a redeploy or a rollback. | | `ready_at`, `promoted_at` | When it went live. | | `finished_at` | When it went live, failed or was cancelled. | | `created_at`, `updated_at` | When it was created and last changed. | | `replica_list` | Each of its replicas ([Replica](#replica)). Only the request for one deployment returns it. | ## Replica counts | Field | Meaning | | --- | --- | | `healthy` | Replicas passing their health checks. | | `unhealthy` | Replicas that failed their most recent health checks. | | `starting` | Replicas that are `provisioning` or `starting`. | | `total` | The three above together. | ## Build | Field | Meaning | | --- | --- | | `uuid` | The build's ID. | | `status` | `queued`, `running`, `succeeded`, `failed` or `canceled`. | | `strategy` | `dockerfile` or `railpack`, once the build has chosen ([Builds](/docs/apps/how-apps-work/#builds)). | | `error` | Why it failed or was cancelled. | | `attempts` | How many times it has started. A build that stops reporting starts once more. | | `log_bytes` | How much log it has written in all ([The build log](/docs/apps/read-a-deployment/#the-build-log)). | | `started_at`, `finished_at`, `created_at` | When it started, ended and was queued. | ## Replica | Field | Meaning | | --- | --- | | `location` | The code of its region, such as `fra`. | | `state` | Where it is ([Replicas](/docs/apps/read-a-deployment/#replicas)). | | `health_failures` | How many health checks it has failed in a row. | | `last_health_at` | When it was last checked. | | `last_error` | What last went wrong, such as `Health check on /healthz: HTTP 500`. | | `created_at` | When it was placed. | | `drained_at` | When it stopped taking requests. | ## Domain | Field | Meaning | | --- | --- | | `hostname` | The name, such as `www.example.com`. | | `kind` | `platform` for the platform name, `custom` for your own domain. | | `verified`, `verified_at` | Whether the domain is verified, and when. | | `url` | `https://` and the hostname, once verified. | | `certificate` | `none`, `pending` or `issued` ([Certificates](/docs/apps/custom-domains/#certificates)). | | `verification` | The TXT record to publish, as `type`, `name` and `value`, until the domain is verified. | | `cname_target` | Where a custom domain points: the app's platform name. `null` for the platform name. | | `created_at` | When it was added. | ## Environment variable | Field | Meaning | | --- | --- | | `key` | The variable's name. | | `secret` | `true` when its value is hidden in build logs ([Secret and plain values](/docs/apps/environment-variables/#secret-and-plain-values)). | | `target` | `both`, `build` or `runtime` ([Where a variable reaches](/docs/apps/environment-variables/#where-a-variable-reaches)). | | `value` | Always `null`. The API never returns a value. | | `hint` | The value's last four characters, or `null` when it is shorter than eight characters. | | `readable` | `false` when the platform cannot read the saved value, which then needs saving again. | | `created_at`, `updated_at` | When it was created and last changed. | ## Status words | Field | Words | In the dashboard | | --- | --- | --- | | App `status` | `active`, `suspended` | **Suspended**. An active app shows **Live**, **Not live**, **Failed**, **Not deployed** or the status of its deployment on the way ([Find your apps in the dashboard](/docs/apps/#find-your-apps-in-the-dashboard)). | | Deployment `status` | `queued`, `building`, `deploying`, `ready`, `failed`, `canceled`, `superseded` ([Deployments](/docs/apps/how-apps-work/#deployments)) | **Queued**, **Building**, **Deploying**, **Ready**, **Failed**, **Cancelled**, **Superseded** | | Build `status` | `queued`, `running`, `succeeded`, `failed`, `canceled` | `Waiting for a builder`, `Building`, `Built`, `Build failed`, `Cancelled` | | Replica `state` | `provisioning`, `starting`, `healthy`, `unhealthy`, `draining`, `stopped`, `failed` ([Replicas](/docs/apps/read-a-deployment/#replicas)) | **Provisioning**, **Starting**, **Healthy**, **Unhealthy**, **Draining**, **Stopped**, **Failed** | | Domain `certificate` | `none`, `pending`, `issued` | `No certificate yet`, `Certificate on its way`, `Certificate issued` | | Variable `target` | `both`, `build`, `runtime` | **Build and runtime**, **Build only**, **Runtime only** | ## Related - [Apps](/docs/apps/) - [Apps limits](/docs/apps/limits/) - [Errors](/docs/api/errors/) # Apps limits > The limits on apps, their settings, variables, domains, builds, deployments and API requests, in one place. Source: https://www.coritan.com/docs/apps/limits/ This page lists every fixed limit on Apps. The page that covers each feature explains how to work within it. ## Apps | What | Limit | | --- | --- | | Apps per account or organization | Set by the platform. The **Apps** list shows it with how many you have, such as `3 of 20 apps`. Through the API, `limits.apps` in the answer to `GET /api/v1/client/apps/regions` gives it, where `0` means no limit, and `limits.apps_used` how many you have ([Regions, sizes and limits](/docs/apps/#regions-sizes-and-limits)). A deleted app does not count. | | Source type | Chosen when you create the app. It cannot change. | | Name on the platform | Chosen when you create the app. It cannot change. | ## Settings | Setting | Limit | | --- | --- | | Name | 1–100 characters on one line. | | Name on the platform | 3–63 lowercase letters, digits and hyphens, starting and ending with a letter or a digit, with no hyphens in both the third and fourth places. Unique on the platform, and not a reserved word ([Names on the platform](/docs/apps/create-an-app/#names-on-the-platform)). | | Repository address | An `https://` address on a public host, up to 500 characters, with no user name, token, query or fragment. | | Branch | A name git accepts, up to 200 characters. | | Directory | A path inside the repository, up to 255 characters. | | Access token | Up to 1000 characters. | | Image reference | Up to 500 characters, on a public registry. It cannot be in the platform's own registry. | | Install, build and start commands | Up to 500 characters each, on one line. | | Dockerfile path | A path inside the repository, up to 255 characters. | | Framework | Up to 32 lowercase letters, digits, dots, plus signs and hyphens, starting with a letter or a digit. | | Port | 1–65535. | | Health check path | Starts with `/`, up to 255 characters. | ## Size, regions and replicas | What | Limit | | --- | --- | | Size | `small`, `medium` or `large` ([Replicas and regions](/docs/apps/how-apps-work/#replicas-and-regions)). | | Regions | Every region that can run apps, or up to 20 named regions. | | Replicas in each region (`min_replicas`) | 1–5. | | `max_replicas` | Up to 10. The number of replicas does not grow above `min_replicas`. | ## Environment variables | What | Limit | | --- | --- | | Variables per app | 200. | | Name | Upper-case letters, digits and underscores, not starting with a digit, up to 128 characters. Some names are set by the platform ([Names and values](/docs/apps/environment-variables/#names-and-values)). | | Value | 32 KiB. | | All variables together | 256 KiB, counting each name and value. | ## Domains | What | Limit | | --- | --- | | Custom domains per app | 20. The platform name does not count. | | Hostname | Up to 253 characters. Wildcard names are not accepted. | | Hold on a hostname another app is verifying | 72 hours from when that app added it ([A hostname in use elsewhere](/docs/apps/custom-domains/#a-hostname-in-use-elsewhere)). | ## Builds and deployments | What | Limit | | --- | --- | | Wait for a build to start | 6 hours. After that, the build fails with `No builder took the build within 6 hours.` | | Build time | 1 hour. | | Tries for one build | 2. A build that stops reporting starts once more. | | Build log | The last 64 KB is kept. After 32 MB of log, the rest of the build's log is not kept. | | Time for a deployment to finish | 6 hours. After that, it fails. | | Time for a replica's image to be in place | 20 minutes. | | Time for a replica to become healthy | 10 minutes from when it starts. | | Health check answer | 5 seconds. | | Failed health checks before a replica stops taking requests | 3 in a row. | | Time a region can have no room for a replica | 1 minute. After that, the region fails. | | Stop after taking no more requests | About 1 minute. | | Removal of a previous deployment's replicas | 30 minutes after they stop taking requests. | [How Apps works](/docs/apps/how-apps-work/) explains each of these. ## Push webhook | What | Limit | | --- | --- | | Delivery size | 10 MB. | | How long a delivery's ID is remembered | 1 day ([What a push deploys](/docs/apps/push-webhook/#what-a-push-deploys)). | ## Requests We count these requests over a rolling window. A request over a limit fails, and does not count towards it. | Requests | Most per window | Window | Counted for | | --- | --- | --- | --- | | Creating apps | 20 | 1 hour | Your account, or the organization | | Changes: every request that creates, changes, deploys or deletes | 120 | 1 hour | Each person | | Deployments: deploy, redeploy and roll back | 30 | 10 minutes | Each app | | Domain checks | 30 | 10 minutes | Each app | | Pushes that deploy | 30 | 5 minutes | Each app | A request over a limit answers `429`, with the length of the window in `retry_after_seconds` and in the `Retry-After` header: ```json { "detail": { "error": "rate_limited", "message": "Too many deployments of this app. Wait a few minutes.", "retry_after_seconds": 600 } } ``` The push webhook answers `429` in its own shape ([The webhook's answers](/docs/apps/push-webhook/#the-webhook-s-answers)). Every API request also counts towards the limits for your account as a whole, which [Rate limits](/docs/api/rate-limits/) describes. ## Lists | What | Limit | | --- | --- | | Apps per page | `limit` takes 1–100, and is 50 when left out. | | Deployments per page | `limit` takes 1–100, and is 20 when left out. | | Deployments that come with an app | The 5 newest. | ## Related - [Apps](/docs/apps/) - [App and deployment fields](/docs/apps/fields/) - [Troubleshoot Apps](/docs/apps/troubleshooting/) # Troubleshoot Apps > Fixes for builds that fail, replicas that do not become healthy, regions left out, changes that have not applied and requests that answer 404. Source: https://www.coritan.com/docs/apps/troubleshooting/ This page covers what goes wrong once you deploy an app. A failed build or deployment says why on its page in the dashboard and in its `error` field ([Read a deployment and its build log](/docs/apps/read-a-deployment/)), and the messages below are the ones it shows. The errors you can hit while you create or change something are on that task's own page. ## Apps is not in the dashboard The sidebar shows **Apps** under **Products** only while Apps is on for the platform. While it is off, the Apps pages show `Page not found`. The dashboard asks the platform once in each browser tab. When Apps has been turned on since, open the dashboard in a new tab. When the dashboard cannot ask, the Apps page shows `Could not reach Apps`: check your connection and select **Try again**. ## A button is missing or turned off **New app…** and **Create app** are turned off : You have as many apps as you may, and the list says `Delete an app to create another.` Delete an app you no longer need ([Delete an app](/docs/apps/app-settings/#delete-an-app)). **Deploy…** and **Redeploy…** are turned off : The app is suspended, and its page shows `This app is suspended` ([A suspended app](/docs/apps/app-settings/#a-suspended-app)). There is no **Redeploy…** : Nothing serves the app yet, so there is nothing to run again. Select **Deploy…** instead. A deployment has no **Roll back to this…** : It never went live, it is the one serving now, or the app is suspended ([Roll back to an earlier deployment](/docs/apps/roll-back-a-deployment/)). **Add domain…** or **Add variable…** is turned off : The app has as many custom domains or variables as it may ([Apps limits](/docs/apps/limits/)). Remove one first. ## Every Apps request answers 404 When every Apps request answers `404` with `{"detail": "Not Found"}`, Apps is turned off on the platform. No request reaches it until it is turned on again. When one app answers `404` with `App not found`, the app belongs to another account or organization, or it was deleted. An organization's app answers only under `/api/v1/orgs/{org_slug}/apps`, so check that you use the organization's path, and that you are a member of the organization. ## The build cannot fetch the repository `git could not fetch main from https://github.com/example/web-shop.` : The branch does not exist, or the repository is private and the app has no access token that can read it. Check the branch name, and save a token that can read the repository ([Change the settings](/docs/apps/app-settings/#change-the-settings)). The lines after the message are git's own output. `The repository host github.com does not resolve.` : The host name in the repository address does not exist in DNS. Check the address. `The repository host git.example.com resolves to 10.0.0.5, which is not a public address.` : The repository is on a private network. The build reaches only public hosts, so move the repository to one. `main has moved past commit 9f2c1e7b4a6d, and github.com does not serve an older commit by its id. Deploy the branch again.` : You deployed a commit that is no longer the tip of its branch, and the git host does not serve older commits by their ID. Deploy the branch, or deploy the commit from a branch whose tip it is. `The repository token cannot be read on this platform. Save the token again in the app's settings.` : The platform can no longer read the saved access token. Save the token again, then deploy. ## The build cannot find the app or its Dockerfile `The app directory apps/web is not in the repository at this commit.` : The app's directory does not exist in the commit you deployed. Check its spelling and case, or clear it to build from the root ([Change the settings](/docs/apps/app-settings/#change-the-settings)). `There is no Dockerfile at docker/Dockerfile in the repository at this commit.` : The Dockerfile path does not point at a file. A path is looked for in the app's directory first, then at the root of the repository ([Builds](/docs/apps/how-apps-work/#builds)). Correct the path, or clear it to let the build choose. ## Railpack cannot plan the app `Railpack could not plan this app: …` : Railpack could not work out how to build or start the app from its files. The rest of the message is Railpack's reason. Check that the app's directory is right, and set the start command, or add a Dockerfile to take over the build. [Railpack's documentation](https://railpack.com) lists what it can build. ## The image build fails `The image build failed (buildctl exited with 1).` : A step of the build failed, such as installing packages or running the build command. The lines after the message, and the end of the build log, show which step and why ([The build log](/docs/apps/read-a-deployment/#the-build-log)). Fix the step, then deploy again. `The build variable API_KEY cannot be read on this platform. Save it again.` : The platform can no longer read a variable's saved value. Set its value again ([A value that cannot be read](/docs/apps/environment-variables/#a-value-that-cannot-be-read)). ## The build takes too long or does not start `The build ran for more than 60 minutes and was stopped.` : A build can run for an hour. Make the build faster, or build the image yourself and create an app that runs it. `No builder took the build within 6 hours.` or `The builder stopped responding, and this build had already been retried once.` : The platform could not run the build. Deploy again later, and [contact support](/docs/support/conversations/) if it happens again. `Builds are not available on this platform yet.` : The platform cannot build apps at the moment, so a git app cannot deploy a new commit. A redeploy without a rebuild, a rollback and an app that runs an image still work. [Contact support](/docs/support/conversations/) for more. ## Replicas do not pass their health check `The app did not pass its health check within 10 minutes. Health check on /: the connection failed` : Nothing answered on the replica's port. The app must listen on the port in its `PORT` variable, on every interface (`0.0.0.0`), and start within 10 minutes ([What your app must do](/docs/apps/how-apps-work/#what-your-app-must-do)). `Health check on /healthz: HTTP 404` : The app answers, but not with a status from 200 to 399 on the health check path. Correct the path ([Change the settings](/docs/apps/app-settings/#change-the-settings)), or make the app answer on it. `Health check on /healthz: no answer within 5 seconds` : The app took longer than 5 seconds to answer. Make the health check path answer quickly, without calling slow services. A deployment whose replicas fail in a region ends with `Replicas could not start in fra: …` and the reason from one of its replicas. The deployment that served the app before keeps serving. ## A replica cannot start in a region `Replicas could not start in fra: No app node in fra has room for another replica of this deployment` : The region has no room for the app's replicas at the moment. Try again later, choose other regions, run fewer replicas in each region, or choose a smaller size ([Change the settings](/docs/apps/app-settings/#change-the-settings)). `The replica's server did not finish installing within 20 minutes` : Placing the replica and pulling its image took more than 20 minutes. A large image takes longer to pull, so make it smaller if you can, then deploy again. `The replica's server could not be created`, `could not be installed` or `could not be started` : The platform could not start the replica. We replace it on our own. When a deployment fails this way again and again, [contact support](/docs/support/conversations/). ## A deployment went live without a region A deployment of an app that runs in every region goes live without a region that failed, and its `error` names the region, such as `Left out sin: No app node in sin has room for another replica of this deployment`. The app serves from its other regions. The next deployment tries the region again, so redeploy the app later ([Redeploy](/docs/apps/deploy-an-app/#redeploy)). ## A deployment was cancelled Its `error` says why: `Canceled` : Someone cancelled it. `A newer deployment was promoted` or `A newer deployment is already serving` : A newer deployment went live first, which is expected when you deploy twice close together. `The app was suspended` or `The app was deleted` : The app stopped being active while the deployment was on its way. ## A deployment did not finish `The deployment did not finish within 6 hours` : A deployment has 6 hours to go live. Look at its replicas for the reason ([Replicas](/docs/apps/read-a-deployment/#replicas)), then deploy again. ## Replicas are taken out of service while the app runs `Taken out of service after 3 failed health checks in a row. Health check on /healthz: HTTP 500` : A running replica failed three health checks in a row. It stopped taking requests, and we started a replacement in its region. Check what the app does on the health check path, fix it and deploy. When every replica of an app fails, they all keep serving so the app is not left with none. ## Files disappear after a deployment Each deployment runs on new replicas, and a replica keeps nothing on its disk from one deployment to the next. Keep data in a database or in [Object Storage](/docs/object-storage/). ## A change has not reached the app Most changes reach the running replicas only with a new deployment. After you save one in the dashboard, a note under the app's header says which you need, with a **Redeploy…** button. Through the API, the answer to a change says `redeploy_required` for a redeploy, and `rebuild_required` for a redeploy with a rebuild ([When a change applies](/docs/apps/app-settings/#when-a-change-applies)). A change to environment variables needs a redeploy too ([Apply a change](/docs/apps/environment-variables/#apply-a-change)). ## A domain does not verify, or a push does not deploy [Add a custom domain](/docs/apps/custom-domains/#troubleshooting) covers domains that do not verify and certificates that stay `Certificate on its way`. [Deploy on every push](/docs/apps/push-webhook/#troubleshooting) covers pushes that do not deploy. ## Related - [How Apps works](/docs/apps/how-apps-work/) - [Read a deployment and its build log](/docs/apps/read-a-deployment/) - [Apps limits](/docs/apps/limits/) - [Contact support from the dashboard](/docs/support/conversations/) # Object Storage > Store files in S3-compatible buckets, reach them with scoped access keys from any S3 client, and browse them in the dashboard. Source: https://www.coritan.com/docs/object-storage/ In the dashboard: - /solutions/object-storage: https://www.coritan.com/solutions/object-storage - /dashboard/storage: https://www.coritan.com/dashboard/storage - /dashboard/storage/…/overview: https://www.coritan.com/dashboard/storage Object Storage keeps files as *objects* in *buckets* and serves them over the S3 API, the interface that Amazon S3 made standard. The AWS CLI, rclone, s3cmd, the AWS SDKs and most backup tools work with it once you give them our endpoint and an access key. You can also upload, download and delete files in the dashboard. Use it for backups, media, build artefacts, logs and other data that you read and write as whole files. Every bucket is private. Each request needs a signature from one of your access keys, or a presigned URL that you created. ## What a service includes An Object Storage service gives you: - A storage allowance, set by the plan, and a rate per GB-month for what your buckets hold above it. - A *home region*, which you choose when you order. The service's first bucket goes there. - Up to 25 buckets, in the home region or in any other region that offers Object Storage. - Access keys that sign S3 requests. Each key is read-only or read-write, and works on one bucket or on every bucket on your account. Every bucket name on your account starts with your *account namespace* and a hyphen, such as `u7-`. The namespace comes from your account, so all your services share it. [Bucket names](/docs/object-storage/buckets/#bucket-names) gives the rules. Each region has its own endpoint, `https://s3..coritan.com:7337`, where `` is the region's code. For Frankfurt, the code is `fra` and the endpoint is `https://s3.fra.coritan.com:7337`. A bucket answers at the endpoint of the region you created it in. ## Your Object Storage services Select **Object Storage** in the [dashboard](https://www.coritan.com/dashboard/storage) sidebar. The list shows every Object Storage service on your account that has not ended. Each row shows the **Service** name and plan, the home **Region**, the **Status**, how much of the allowance the buckets use under **Storage**, and the number of **Buckets** and **Keys**. **Since** is the date you ordered it. - Filter the list with **All**, **Active**, **Pending** or **Suspended**. `Pending` covers a service that waits for payment or that we are setting up. - Type in the search box to find a service by its name, plan or region. - Select **Order Object Storage** to open the order page, as [Order Object Storage](/docs/object-storage/order-object-storage/) describes. Select a row to open the service. The header shows its name, status, plan, home region and service number. **Order another** opens the order page for one more service. The tabs are **Overview**, **Buckets**, **Access keys** and **Billing**. A banner above the tabs appears while the service cannot be used in full: **Waiting for payment** : The first invoice is unpaid. We set the service up as soon as you pay it. **Open invoices** lists your invoices. **Setting up** : We are creating the service. This takes under a minute. Reload the page to see the result. **Suspended** : The service is suspended, usually because an invoice is overdue. Its access keys stop working and its objects stay where they are. You can change buckets and keys again once the service is active. [Failed payments and suspended services](/docs/billing/failed-payments/) explains how to lift a suspension. ## Read the Overview tab The **Overview** tab opens first. When your buckets hold more than the plan's allowance, a warning such as `12.5 GB above the plan` appears at the top of the tab, with the plan's rate per GB-month for storage above the allowance. Its **Change plan** button opens the **Billing** tab, where a plan change moves the service to another billing cycle with the same allowance. To get a larger allowance, move to a larger plan as [Change the plan](/docs/object-storage/order-object-storage/#change-the-plan) describes, or delete what you no longer need. **Storage**, **Objects**, **Buckets** and **Access keys** : What the service's buckets hold against the allowance, how many objects they hold, how many buckets the service has out of its limit, and how many keys you created on it. We measure storage and objects every hour, so these figures trail recent uploads and deletions. **Endpoints** : One row for each region the service uses, with the endpoint to copy: the home region first, then each other region that holds one of its buckets. **Bucket prefix** is the prefix every bucket name on your account starts with. **Signature** says that clients sign requests with AWS Signature Version 4 and that any region string works. **Connect a client** : Setup commands for the AWS CLI, rclone and s3cmd, filled in with your endpoint. Replace the placeholders with a key from the **Access keys** tab. [Connect an S3 client](/docs/object-storage/connect-an-s3-client/) explains each setting. **Stored over time** : A bar chart of what the service stored over the last `7d`, `30d` or `90d`, from the hourly measurements. The chart shows at most 120 bars, so in a longer window each bar stands for several hours and shows the highest measurement among them. A dashed line marks the plan's allowance. `No measurements yet` means the chart has nothing to show for that window. **Service** : The **Plan**, the storage it has **Included**, the rate **Above the plan**, the **Billing** cycle, the date it **Renews**, the **Home region**, the **Namespace**, the **Bucket limit**, what happens **After cancellation**, and the date it started (**Since**). **Open Billing** opens the **Billing** tab. **Tags** : Labels to find the service by, as [Tag your services](/docs/get-started/tags/) describes. ## What you can do Set up storage: - [Order Object Storage](/docs/object-storage/order-object-storage/), and manage the plan on the **Billing** tab - [Create and delete buckets](/docs/object-storage/buckets/), the **Buckets** tab - [Create and revoke access keys](/docs/object-storage/access-keys/), the **Access keys** tab Use it: - [Connect an S3 client](/docs/object-storage/connect-an-s3-client/) with the endpoint and a key - [Upload, download and delete objects](/docs/object-storage/objects/) in the dashboard's object browser, or share a file with a presigned URL Pay for it and fix problems: - [How Object Storage is billed](/docs/object-storage/usage-and-billing/) - [Object Storage limits](/docs/object-storage/limits/) - [Troubleshoot Object Storage](/docs/object-storage/troubleshooting/) The public page at `https://www.coritan.com/solutions/object-storage` describes the product. The order page in the dashboard lists the regions and plans you can actually order. ## How Object Storage is billed Each Object Storage service is a service of its own, on the plan and billing cycle you order. The plan's price covers its storage allowance. We measure what your buckets hold every hour, and the plan sets a rate per GB-month for storage above the allowance. [How Object Storage is billed](/docs/object-storage/usage-and-billing/) explains how we measure storage and what the dashboard shows when your buckets go above the allowance. [Billing](/docs/billing/) covers invoices, credit and payment. ## Limits and availability - A service holds up to 25 buckets. There is no fixed limit on access keys. - A bucket stays in the region you create it in, and you cannot rename it. It answers only at that region's endpoint. - Clients connect over HTTPS on port 7337 and address buckets in the path, as in `https://s3.fra.coritan.com:7337/u7-assets/photo.jpg`. Bucket names as hostnames, such as `u7-assets.s3.fra.coritan.com`, do not resolve. - We do not offer public buckets, custom domains or versioning. - A plan change keeps the allowance. To get more storage included, order a larger plan. [Object Storage limits](/docs/object-storage/limits/) lists every limit. ## With the API `GET /api/v1/client/object-storage/services` lists the Object Storage services on your account that have not ended, newest first, as `items` with a `total`. ```bash curl https://api.coritan.com/api/v1/client/object-storage/services \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "items": [ { "id": 1207, "service_id": 1207, "label": "Object Storage 250 GB", "hostname": null, "status": "active", "product_name": "Object Storage 250 GB", "billing_cycle": "monthly", "region": "fra", "location_id": 1, "namespace": "u7", "bucket_prefix": "u7-", "quota_bytes": 268435456000, "used_bytes": 48318382080, "object_count": 1284, "bucket_count": 2, "key_count": 3, "overage_per_gb_month": 0.02, "created_at": "2026-09-01T10:00:00", "next_due_date": "2026-10-01T00:00:00" } ], "total": 1 } ``` `id` and `service_id` hold the same number: the service ID that every other Object Storage request takes. `label` is the name the dashboard shows, which is the service's `hostname` when it has one and the plan name otherwise. `region` is the home region's code and `location_id` its location. `region` is `null` until setup finishes. `bucket_prefix` is the namespace with its hyphen. `quota_bytes` is the allowance in bytes, where a GB is 1024³ bytes, or `null` on a plan without one. `used_bytes` and `object_count` add up the service's buckets at the last hourly measurement. `key_count` counts every key on the service, including keys that are turned off. `overage_per_gb_month` is the plan's rate for each GB-month above the allowance, in US dollars. `GET /api/v1/client/object-storage/{service_id}` returns one service with the same fields and these as well: `endpoints` : A list of `region` and `endpoint` pairs: the home region first, then each other region that holds one of the service's buckets. `overage_gb` : How many GB the buckets hold above the allowance, at the last measurement. `grace_days`, `max_buckets` and `presign_max_seconds` : How many days we keep objects after the service ends, the most buckets the service may hold, and the longest a presigned URL from the API lasts, in seconds. They are `7`, `25` and `3600`. A service ID that is not an Object Storage service on your account answers `404` with `Object storage service not found`. The [Object Storage API reference](/docs/api/reference/client/object-storage/#op-get-api-v1-client-object-storage-services) lists every field. ## API - `GET /api/v1/client/object-storage/services`: The account's object storage services with their headline numbers (https://www.coritan.com/docs/api/reference/client/object-storage/#op-get-api-v1-client-object-storage-services) # Order Object Storage > Choose a plan and a region, name a first bucket if you want one, pay on the order's page, and change or cancel the plan on the Billing tab. Source: https://www.coritan.com/docs/object-storage/order-object-storage/ In the dashboard: - /dashboard/storage: https://www.coritan.com/dashboard/storage - /dashboard/order/object-storage: https://www.coritan.com/dashboard/order/object-storage - /dashboard/storage/…/billing: https://www.coritan.com/dashboard/storage An Object Storage service starts with a plan, which sets its storage allowance, and a home region. You order it on its own order page in the dashboard. Setup takes under a minute once the first invoice is paid. You can name the service's first bucket in the order, or create buckets later. ## Before you begin - Sign in to the [dashboard](https://www.coritan.com/dashboard/storage). - Choose a home region close to the machines and people that use the data. Each bucket answers only at its own region's endpoint. You can create buckets in other regions later. - For the first invoice, credit on your account pays first. Otherwise you pay by card or PayPal on the page the order lands on ([How the first payment works](/docs/get-started/order-a-service/#how-the-first-payment-works)). ## Place the order Open the order page in one of these ways: - In the sidebar, select **Object Storage**, then **Order Object Storage**. With no service yet, the same button is in the middle of the page. - In a service's panel, select **Order another** in its header. - On [**Order a service**](https://www.coritan.com/dashboard/order), select the **Object Storage** card under **Storage**. The page has three numbered sections, and the summary under **Your order** sits beside them. On a phone the summary follows the sections, and **Review order** at the bottom of the screen takes you to it. 1. Under **Plan**, choose a plan. Each plan shows its allowance, the rate for storage above it and its price, such as `250 GB included · $0.02 per GB-month above that`. 2. Choose the billing cycle with the buttons beside the **Plan** heading, such as **Monthly**. A longer term names what it saves, such as `Annually · save 20%`. 3. Under **Region**, choose the home region. Each card shows the region's name and its code, such as `Frankfurt` and `fra · Germany`. The page lists only the regions that can take a bucket right now, and it starts with one near you when there is one. 4. To create a bucket straight away, type its name in **Bucket name** under **First bucket**, such as `assets`. We put your account namespace in front, and the hint shows the full name, such as `We create it as u7-assets.` Leave the field empty to create buckets later. [Bucket names](/docs/object-storage/buckets/#bucket-names) gives the rules. 5. Check the summary. It lists the plan with its billing cycle, the region and any first bucket, with **Change** beside each to go back to its section. The **Total** shows the price, any one-time setup fee and what is due today, and the line under it says how the first invoice is paid. 6. Select the button under the summary. It reads **Place order and pay** when you pay after placing the order, and **Place order** when your credit pays for it or your account is billed in arrears. While something is missing, the button is greyed out and the line under it names what, such as `Choose a region.` If we refuse the order, **Could not place the order** appears above the button with the reason, and your choices stay as they were. Once you place it, the order's own page opens. When its title is **Order placed, payment due**, pay under **Pay invoice** with account credit, a saved card or PayPal account, or a new card ([Pay and follow the order](/docs/get-started/order-a-service/#pay-and-follow-the-order)). Setup starts once the payment goes through. To pay later, leave the page and pay the invoice under [Invoices](/docs/billing/invoices/). ## Result The order's page follows the setup and updates on its own. Its title becomes **Your order is ready** when the service is active, and its button then opens the service. We email you when it is ready. The new service appears on the **Object Storage** page. Its status is `pending` until the first invoice is paid, `provisioning` while we set it up and `active` when it is ready. That page does not refresh on its own, so reload it to see the change. Until the service is ready, its panel shows **Waiting for payment**, with **Open invoices**, or **Setting up**. When setup finishes, the **Activity** table on the service's **Billing** tab lists it with a line such as `Object storage ready in Frankfurt with 250 GB included. Bucket u7-assets created.` Next, [create an access key](/docs/object-storage/access-keys/) on the **Access keys** tab and [connect an S3 client](/docs/object-storage/connect-an-s3-client/). ## Manage the plan on the Billing tab Open the service and select the **Billing** tab. **Current plan** : The **Product**, **Plan**, **Price**, **Status**, **Next renewal** date, the date the service was **Set up**, and its **Service ID**. **Activity** : Setup, renewals, plan changes and other events, each with **When**, **Event**, **Details** and **By**. **Cancel service** : Ends the service, as [Cancel the service](#cancel-the-service) describes. ### Change the plan Each Object Storage size is a product of its own. **Change plan…** on the **Current plan** card moves the service to another billing cycle of the same size, and the allowance stays the same. [Change a service's plan](/docs/billing/change-plan/) walks through the dialog and how we bill the switch. When the size has no other billing cycle, the card says `No other plan to switch to`. To move to a larger or smaller allowance: 1. Order a new service on the plan you want, as [Place the order](#place-the-order) describes. 2. On the new service's **Buckets** tab, create the buckets you need. Bucket names are unique across your account, so give them new names, such as `u7-assets-2`. 3. Copy your objects across with an S3 client and a key whose scope is **All buckets on this account**. Such a key reaches the buckets of both services. With rclone, and both buckets in one region, run `rclone sync coritan:u7-assets coritan:u7-assets-2`. [Connect an S3 client](/docs/object-storage/connect-an-s3-client/) sets up the `coritan` remote. 4. Point your applications at the new buckets and give them keys from the new service. 5. Cancel the old service, as described below. ### Cancel the service 1. On the **Cancel service** card, select **Cancel service…**. 2. Under **When**, choose **At the end of the current term** or **Immediately**. 3. Type `cancel` and select **Cancel service**. [Cancel a service](/docs/billing/cancel-a-service/) explains both choices and what happens to unpaid invoices. When the service ends, its access keys stop working and it leaves the **Object Storage** page. We keep the objects in its buckets for 7 more days and then delete them. The dashboard and the service's keys cannot reach the objects in that time, and the bucket names stay taken until we delete them. > [!CAUTION] > Cancelling deletes every object in the service's buckets 7 days after the service ends, and you cannot undo it. Copy out anything you need before the service ends. ## Troubleshooting **Could not load the regions** : The list of regions did not load. The message under it gives the reason. Reload the page. **No region has a gateway right now** : No region can take a bucket at the moment, so you cannot order Object Storage. Try again later, or [contact support](/docs/support/conversations/). `No Object Storage plans yet` : Object Storage is not in the catalogue for your account. [Contact support](/docs/support/conversations/) if you expected it. **Could not load the plans** : The plans did not load. Select **Try again**, or reload the page. `Fix the bucket name, or leave it empty.` : The name in **Bucket name** breaks a rule, and the message under the field says which one, such as `Use lowercase letters, digits, hyphens and dots, and start and end with a letter or digit.` Correct the name, or clear the field and create the bucket later. The service stays on **Waiting for payment** : The first invoice is unpaid. Select **Open invoices** and [pay the invoice](/docs/billing/invoices/). Setup starts once it is paid. The **Activity** line ends with `The first bucket was not created:` and a reason : The service works, but we could not create the bucket you named. The reason says why, such as `Bucket names are 3 to 63 characters including the account prefix`. Create the bucket on the **Buckets** tab ([Create and delete buckets](/docs/object-storage/buckets/)). The first bucket is missing, and the **Activity** line does not mention it : Your account already has a bucket with that name, or had one that you deleted in the last 7 days, so we skipped the name. Create a bucket with another name on the **Buckets** tab. The status says `failed` : Setup did not finish. The **Activity** table gives the reason, such as `That region does not offer object storage`. [Contact support](/docs/support/conversations/) with the service ID. `No other plan to switch to` : The size you have comes in one billing cycle only. To change the allowance, follow [Change the plan](#change-the-plan). ## Related - [Create and delete buckets](/docs/object-storage/buckets/) - [Create and revoke access keys](/docs/object-storage/access-keys/) - [How Object Storage is billed](/docs/object-storage/usage-and-billing/) - [Order a service](/docs/get-started/order-a-service/) - [Cancel a service](/docs/billing/cancel-a-service/) ## With the API Every Object Storage plan is a product whose `module_name` is `object_storage`. List the catalogue with [`GET /products/`](/docs/api/reference/client/catalog/#op-get-api-v1-products), which needs no authentication. Each product's `id` is the `product_id` to order, and each entry in its `pricing` list has the `pricing_id` for one billing cycle. ```bash curl "https://api.coritan.com/api/v1/products/?per_page=200" ``` List the regions that can take a bucket with [`GET /api/v1/client/object-storage/regions`](/docs/api/reference/client/object-storage/#op-get-api-v1-client-object-storage-regions): ```bash curl https://api.coritan.com/api/v1/client/object-storage/regions \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "regions": [ { "id": 1, "code": "fra", "name": "Frankfurt", "country_code": "DE", "endpoint": "https://s3.fra.coritan.com:7337" } ] } ``` `id` is the `location_id` to order with. The list changes as regions gain or lose storage, so read it before each order. An empty list means you cannot order Object Storage right now. Place the order with [`POST /api/v1/services/order`](/docs/api/reference/client/services/#op-post-api-v1-services-order): ```bash curl -X POST https://api.coritan.com/api/v1/services/order \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "product_id": 58, "pricing_id": 171, "config": { "location_id": 1, "bucket_name": "assets" } }' ``` `config.location_id` : Required. The `id` of a region from the list above. `config.bucket_name` : Optional. The first bucket's name, with or without your prefix. We change it to lowercase and add the prefix when it is missing. `hostname` : Optional. The name the dashboard lists the service under, up to 100 characters. Without it, the service takes the plan's name. Object Storage takes no add-ons. An order with `addons` answers `422` with `Object Storage 250 GB does not take add-ons. Order each product on its own.`, where the plan's name comes first. A new order answers `201`: ```json { "service": {"id": 1207, "status": "pending", "billing_cycle": "monthly", "amount": 5.0, "hostname": null}, "addons": [], "invoice_id": 5540, "requires_payment": true, "checkout_available": true, "amount_due": "5.00", "message": "Invoice created; pay to start provisioning" } ``` When `requires_payment` is `true`, pay `invoice_id` as [Pay an invoice](/docs/billing/invoices/#with-the-api) shows. Setup starts once it is paid. [Order a service](/docs/get-started/order-a-service/#with-the-api) lists the other values of `message`, including `Order already submitted` for a repeat of the same order within two minutes. A `config` that fails the checks answers `422` with `{"detail": {"errors": [...]}}`. The errors are `location_id is required and must be an integer`, `location_id must be a positive integer`, or a bucket name rule such as `Bucket names use lowercase letters, digits, hyphens and dots, and start and end with a letter or digit`. The order does not check that the location has storage. An order for a location that is not in the regions list is accepted, then fails during setup with `That region does not offer object storage`, and the service's status becomes `failed`. To change the billing cycle or cancel through the API, follow [Change a service's plan](/docs/billing/change-plan/#with-the-api) and [Cancel a service](/docs/billing/cancel-a-service/#with-the-api) with the service ID. ## API - `GET /api/v1/client/object-storage/regions`: Regions with a gateway to put a bucket in, with each one's endpoint (https://www.coritan.com/docs/api/reference/client/object-storage/#op-get-api-v1-client-object-storage-regions) - `POST /api/v1/services/order`: Order a platform service, and any add-ons bought with it (https://www.coritan.com/docs/api/reference/client/services/#op-post-api-v1-services-order) # Create and delete buckets > Create a bucket in any region that offers Object Storage, follow the naming rules, and delete a bucket with or without its objects. Source: https://www.coritan.com/docs/object-storage/buckets/ In the dashboard: - /dashboard/storage/…/buckets: https://www.coritan.com/dashboard/storage A *bucket* holds objects under one name. Each Object Storage service holds up to 25 buckets, and each bucket lives in one region. You create and delete buckets on the service's **Buckets** tab, or through the API. Access keys cannot create or delete buckets, so S3 commands such as `aws s3 mb` are refused. ## Before you begin - Sign in to the [dashboard](https://www.coritan.com/dashboard/storage) and open the service. - The service must be `active` to create or delete a bucket. While it waits for payment, while we set it up and while it is suspended, you can browse its buckets and download objects. ## Bucket names Every bucket name on your account starts with your account namespace and a hyphen, such as `u7-`. The **Name** field shows this prefix in front of what you type, and every client uses the whole name. The **Bucket prefix** row on the **Overview** tab shows yours. A bucket name must follow these rules: - The whole name, prefix included, is 3–63 characters long. With the prefix `u7-`, the part you type can be up to 60 characters. - It uses only lowercase letters, digits, hyphens (`-`) and dots (`.`). - It starts and ends with a letter or a digit. - A dot cannot sit next to another dot or next to a hyphen, so `u7-my..files` and `u7-my.-files` are refused. Two hyphens in a row are allowed. We change uppercase letters to lowercase. When you type the prefix yourself, we do not add it a second time. Names are unique across your account, so two of your services cannot both have a bucket called `u7-assets`. After you delete a bucket, its name stays taken for 7 days. You cannot rename a bucket or move it to another region. To change either, create a new bucket and copy the objects into it with an S3 client. ## Create a bucket 1. Open the service and select the **Buckets** tab. 2. Select **Create bucket…**. 3. In **Name**, type the part after your prefix, such as `assets`. The hint shows the full name and its length, such as `Full name: u7-assets · 9/63`. When the name breaks a rule, the message under the field says which. 4. Under **Region**, choose where the bucket lives. The service's home region is selected at first. The list holds every region that can take a bucket right now. 5. Select **Create bucket**. A message confirms it, such as `u7-assets created.`, and the bucket appears in the list. ## Read the bucket list The **Buckets** card says how many buckets the service has out of its limit, such as `3 of 25 on this service.` The list is sorted by name, and each column heading sorts it another way. **Bucket** : The bucket's full name. Select it, or anywhere in its row, to browse its objects ([Upload, download and delete objects](/docs/object-storage/objects/)). **Region** : The code of the region the bucket lives in, such as `FRA`. **Objects** and **Size** : How many objects the bucket held, and their total size, at the last measurement. We measure every bucket once an hour. **Measured** : When we last measured the bucket. `Not yet` means we have not measured it since you created it. **Created** : The date you created the bucket. The menu at the end of each row has **Browse objects** and, while the service is active, **Delete bucket…**. ## Delete a bucket > [!CAUTION] > Deleting a bucket with its contents destroys every object in it straight away. There is no undo and no copy. 1. On the **Buckets** tab, open the bucket's menu and select **Delete bucket…**. 2. Read the dialog. For an empty bucket, it says that you lose nothing. For a bucket that holds objects, it gives how many objects and how much data you would destroy. 3. For a bucket that holds objects, tick the box that offers to destroy them, such as `Destroy the 1,284 objects in it`. Without the tick, we refuse to delete the bucket. 4. Type the bucket's full name, such as `u7-assets`. 5. Select **Delete bucket**, or **Delete bucket and contents** for a bucket that holds objects. A message confirms it, such as `u7-assets deleted.` or `u7-assets deleted with 1,284 objects.` The dialog decides whether a bucket holds objects from the last hourly measurement. When you added objects to an empty bucket since then, the dialog shows no box, and we refuse the delete with `u7-assets is not empty. Delete it with force to destroy its contents as well.` Wait for the next measurement and try again, delete the objects first ([Delete objects and folders](/docs/object-storage/objects/#delete-objects-and-folders)), or delete the bucket through the API with `force=true`. ## Result The bucket leaves the list at once, and: - Its objects are destroyed. Clients that address the bucket get `NoSuchBucket`. - Access keys whose scope is that one bucket stop working. They stay on the **Access keys** tab, marked **Inactive**, until we remove them with the bucket 7 days later. - The bucket's name stays taken for 7 days. A new bucket answers at its region's endpoint, such as `https://s3.fra.coritan.com:7337/u7-assets`, for every key whose scope covers it. A key whose scope is **All buckets on this account** covers it from the start. ## Troubleshooting `A bucket named u7-assets already exists` : Your account already has a bucket with that name, on this service or on another one. Choose another name. `u7-assets was deleted recently and is still being removed. Pick another name, or wait for the removal to finish.` : You deleted a bucket with that name in the last 7 days. Choose another name, or wait until 7 days have passed since the delete. `This service already has 25 buckets, which is the most one service may hold` : Delete a bucket you no longer need, or order another service. The **Create bucket…** button is greyed out while the service is at its limit. An error under **Name**, such as `Dots and hyphens cannot be next to each other.` : The name breaks one of the rules in [Bucket names](#bucket-names). Change it. `No region is available right now` : No region can take a bucket at the moment. Try again later, or [contact support](/docs/support/conversations/). `That region does not offer object storage` : The region stopped taking buckets after you opened the dialog. Choose another region. `u7-assets is not empty. Delete it with force to destroy its contents as well.` : The bucket holds objects that the last measurement did not count. [Delete a bucket](#delete-a-bucket) gives three ways forward. `This service is suspended; it can be changed once it is active` : You can create and delete buckets only while the service is `active`. Pay the overdue invoice ([Failed payments and suspended services](/docs/billing/failed-payments/)), then try again. The same message names the status `pending` while the service waits for payment. **Create bucket…** is missing : The service is not active. The tab says `Buckets can be created and deleted once the service is active; browsing works now.` `Gateway refused the bucket:` followed by a reason : The region's storage refused the request. Try again in a minute. If it keeps happening, [contact support](/docs/support/conversations/) with the whole message. ## Related - [Upload, download and delete objects](/docs/object-storage/objects/) - [Create and revoke access keys](/docs/object-storage/access-keys/) - [Object Storage limits](/docs/object-storage/limits/) - [Troubleshoot Object Storage](/docs/object-storage/troubleshooting/) ## With the API Each request takes the service ID. A service that is not Object Storage on your account answers `404` with `Object storage service not found`. ### List buckets [`GET /api/v1/client/object-storage/{service_id}/buckets`](/docs/api/reference/client/object-storage/#op-get-api-v1-client-object-storage-service-id-buckets) lists the service's buckets, sorted by name: ```bash curl https://api.coritan.com/api/v1/client/object-storage/1207/buckets \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "items": [ { "id": 31, "name": "u7-assets", "region": "fra", "location_id": 1, "location_name": "Frankfurt", "quota_bytes": null, "used_bytes": 48318382080, "object_count": 1284, "usage_measured_at": "2026-09-26T09:00:00", "created_at": "2026-09-01T10:01:00", "deleted_at": null, "endpoint": "https://s3.fra.coritan.com:7337", "url": "https://s3.fra.coritan.com:7337/u7-assets" }, { "id": 32, "name": "u7-backups", "region": "fra", "location_id": 1, "location_name": "Frankfurt", "quota_bytes": null, "used_bytes": 0, "object_count": 0, "usage_measured_at": null, "created_at": "2026-09-26T08:40:00", "deleted_at": null, "endpoint": "https://s3.fra.coritan.com:7337", "url": "https://s3.fra.coritan.com:7337/u7-backups" } ], "total": 2, "bucket_prefix": "u7-", "max_buckets": 25 } ``` Use `id` as the `bucket_id` in the other bucket and object requests, and `name` in S3 clients. `endpoint` is the regional endpoint the bucket answers at, and `url` is the bucket's address there. `used_bytes` and `object_count` come from the last hourly measurement at `usage_measured_at`, which is `null` until the first one. The list leaves out deleted buckets, so `deleted_at` is `null`. [`GET /api/v1/client/object-storage/{service_id}/buckets/{bucket_id}`](/docs/api/reference/client/object-storage/#op-get-api-v1-client-object-storage-service-id-buckets-bucket-id) returns one bucket in the same shape. A bucket that is not on this service, or that you deleted, answers `404` with `Bucket not found`. ### Create a bucket through the API [`POST /api/v1/client/object-storage/{service_id}/buckets`](/docs/api/reference/client/object-storage/#op-post-api-v1-client-object-storage-service-id-buckets) takes the name, with or without your prefix, and optionally a region's `location_id` from [`GET /api/v1/client/object-storage/regions`](/docs/api/reference/client/object-storage/#op-get-api-v1-client-object-storage-regions). Without `location_id`, the bucket goes in the service's home region. ```bash curl -X POST https://api.coritan.com/api/v1/client/object-storage/1207/buckets \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "assets", "location_id": 1}' ``` It answers `201` with the new bucket in the shape above. The errors: | Status | `detail` | Cause | | --- | --- | --- | | `409` | `This service is pending; it can be changed once it is active` | The service is not `active`. The message names its status. | | `409` | `This service already has 25 buckets, which is the most one service may hold` | The service is at its limit. | | `409` | `A bucket named u7-assets already exists` | Your account has a bucket with that name. | | `409` | `u7-assets was deleted recently and is still being removed. Pick another name, or wait for the removal to finish.` | You deleted a bucket with that name in the last 7 days. | | `422` | `Bucket name is required` | The name is empty, or holds only your prefix. | | `422` | `Bucket names are 3 to 63 characters including the account prefix` | The full name is too long. | | `422` | `Bucket names use lowercase letters, digits, hyphens and dots, and start and end with a letter or digit` | The name has another character, or starts or ends with a dot or hyphen. | | `422` | `Bucket names cannot have a dot next to another dot or a hyphen` | The name holds `..`, `.-` or `-.`. | | `422` | `That region does not offer object storage` | The location cannot take a bucket. | | `502` | `Gateway refused the bucket:` and a reason | The region's storage refused the request. | ### Delete a bucket through the API [`DELETE /api/v1/client/object-storage/{service_id}/buckets/{bucket_id}`](/docs/api/reference/client/object-storage/#op-delete-api-v1-client-object-storage-service-id-buckets-bucket-id) deletes an empty bucket. Add `force=true` to destroy its objects as well: ```bash curl -X DELETE "https://api.coritan.com/api/v1/client/object-storage/1207/buckets/31?force=true" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json {"ok": true, "bucket": "u7-assets", "region": "fra", "objects_destroyed": 1284} ``` `objects_destroyed` counts the objects the request destroyed. Without `force`, a bucket that holds objects answers `409` with `u7-assets is not empty. Delete it with force to destroy its contents as well.`, and nothing is deleted. A service that is not `active` answers `409` as it does for a create. ## API - `GET /api/v1/client/object-storage/{service_id}/buckets`: List buckets (https://www.coritan.com/docs/api/reference/client/object-storage/#op-get-api-v1-client-object-storage-service-id-buckets) - `POST /api/v1/client/object-storage/{service_id}/buckets`: Make a bucket in the chosen region, or the service's home region (https://www.coritan.com/docs/api/reference/client/object-storage/#op-post-api-v1-client-object-storage-service-id-buckets) - `GET /api/v1/client/object-storage/{service_id}/buckets/{bucket_id}`: Get bucket (https://www.coritan.com/docs/api/reference/client/object-storage/#op-get-api-v1-client-object-storage-service-id-buckets-bucket-id) - `DELETE /api/v1/client/object-storage/{service_id}/buckets/{bucket_id}`: Remove a bucket (https://www.coritan.com/docs/api/reference/client/object-storage/#op-delete-api-v1-client-object-storage-service-id-buckets-bucket-id) # Upload, download and delete objects > Browse a bucket in the dashboard, upload and download files, create folders, and delete objects, or do the same with presigned URLs. Source: https://www.coritan.com/docs/object-storage/objects/ In the dashboard: - /dashboard/storage/…/buckets: https://www.coritan.com/dashboard/storage An *object* is a file stored in a bucket under a *key*, its full name, such as `photos/2026/beach.jpg`. The **Buckets** tab has an object browser where you upload, download and delete objects. For large files, many files or whole folder trees, use an S3 client ([Connect an S3 client](/docs/object-storage/connect-an-s3-client/)). The object browser does not use your access keys. We sign a short-lived link for each upload, download and folder, and your browser sends the data straight to storage over that link. You can use the browser before you create a key. ## Before you begin - Sign in to the [dashboard](https://www.coritan.com/dashboard/storage) and open the service. - The bucket must exist ([Create a bucket](/docs/object-storage/buckets/#create-a-bucket)). - To upload, create folders or delete objects, the service must be `active`. You can browse and download in any status. ## Open a bucket 1. Open the service and select the **Buckets** tab. 2. Select the bucket's name, or **Browse objects** in its menu. The header shows the bucket's name, its address to copy, and the number of objects and their size from the last hourly measurement. The refresh button beside it lists the folder again. S3 has no real folders. The browser treats the part of a key before each `/` as a *folder*, so `photos/beach.jpg` shows as `beach.jpg` inside the folder `photos`. The list shows folders first, then objects with their **Size** and the time they were **Modified**. - Select a folder to open it. The path above the list starts at **Buckets** and ends at the folder you are in. Select any part of it to go back up. The folder is part of the page address, so the back button and bookmarks work. - To find objects, type the start of their name in the search box, which reads `Keys starting with…`. The list narrows to the keys in the current folder that start with what you typed. - The list shows 200 entries at a time. Select **Load more** for the next 200. ## Upload files 1. Open the folder you want the files in. 2. Select **Upload…** and choose one or more files. You can also drag files from your computer and drop them on the list. 3. Follow each upload in the **Transfers** panel at the bottom right of the page. Select **Cancel** to stop an upload that is running, or **Dismiss** to clear one that has ended. Each file becomes an object whose key is the folder path and the file name, such as `photos/beach.jpg`. > [!WARNING] > An upload replaces an object that has the same key, without asking. We keep no earlier version of it. The browser sends each file in a single request, and it cannot upload a folder with its contents. For a folder tree, or a file of several gigabytes, use an S3 client: `aws s3 sync` and `rclone copy` upload whole folders and send large files in parts. ## Create a folder 1. Open the folder to create the new folder in. 2. Select **New folder…**. 3. Type a name in **Folder name**, such as `photos`, and select **Create folder**. A message confirms `Folder created.` The browser writes an empty object named after the folder with a `/` at the end, such as `photos/`, so the folder shows before it holds anything. A `/` inside the name creates a folder within a folder, such as `2026/09`. We remove a `/` at the start or the end of the name, and a backslash is refused. Folders also appear on their own. When an S3 client uploads `photos/beach.jpg`, the browser shows a `photos` folder even though nobody created it. ## Download an object Select the object's name, or **Download** in its menu. Your browser saves the file under the last part of its key, such as `beach.jpg`. The object browser downloads one object at a time. To download a folder or many objects, use an S3 client, for example `aws --profile coritan s3 cp s3://u7-assets/photos/ ./photos/ --recursive`. ## Delete objects and folders > [!CAUTION] > Deleting an object destroys it at once. There is no undo, and we keep no earlier version to restore. To delete one object: 1. Open the object's menu and select **Delete object…**. 2. Select **Delete object** to confirm. To delete several objects, or a folder with everything in it: 1. Tick the box beside each object or folder. The box in the heading selects every entry in the list. For one folder, you can also select **Delete folder…** in its menu. 2. Select the button that gives the number of entries, such as `Delete 3 items…`. 3. Type `delete` and confirm. The **Transfers** panel follows the deletion. A message then says what was deleted, such as `1,284 objects deleted.` Deleting a folder deletes every object under it, including its folders. The browser deletes up to 10,000 objects under each folder in one run. When a folder holds more, the message `A folder had more than the browser deletes at once; run the delete again for the rest.` appears. Repeat the delete until the folder is gone. An S3 client deletes a large folder in one command, such as `aws --profile coritan s3 rm s3://u7-assets/photos/ --recursive`. ## Share a file with a presigned URL A *presigned URL* is a link that carries its own signature. It lets anyone who has it download, upload or delete one object, whichever it was signed for, without an access key. It works as many times as they like until it expires. The dashboard has no button for it. Create one through the API ([Presign an object](#presign-an-object)), or with an S3 client and one of your keys: ```bash aws --profile coritan s3 presign s3://u7-assets/reports/2026-09.pdf --expires-in 3600 ``` A link from the API lasts 15 minutes unless you ask for longer, and 1 hour at most. We sign it ourselves, so revoking an access key does not affect it. A link from an S3 client is signed with your key, so it stops working when it expires or when you revoke that key. ## Result The browser shows each change as soon as it finishes. The object count and size in the header, on the **Buckets** tab and on the **Overview** tab follow at the next hourly measurement. ## Troubleshooting `Could not reach the storage gateway` : Your browser could not connect to the region's endpoint on port 7337. A firewall, proxy or VPN on your network may block that port. Try another network, or ask whoever runs yours to allow outgoing HTTPS on port 7337. `Could not upload beach.jpg` with `The upload link expired or was refused` : Storage refused the upload. Reload the page and upload the file again. If it keeps happening, [contact support](/docs/support/conversations/). `Could not upload beach.jpg` with `Upload failed` and a number : Storage answered the upload with that HTTP status. Upload the file again. For a large file, use an S3 client, which sends it in parts. `Could not prepare the download` : We could not sign the download link. The message beside it gives the reason. Try again. `Bucket not found` : The bucket was deleted, or the address belongs to another service. Select **All buckets** to go back to the list. `Could not delete 2 objects.` : Storage refused some of the deletions. The rest went through. Run the delete again. **Upload…** and **New folder…** are missing : The service is not `active`, so you cannot change its objects. You can still browse and download. A folder is still there after you deleted everything in it : The folder has an empty folder object, written by **New folder…** or by a client. Delete the folder itself with **Delete folder…**. The size in the header did not change : We measure each bucket once an hour. The header shows the last measurement. ## Related - [Create and delete buckets](/docs/object-storage/buckets/) - [Connect an S3 client](/docs/object-storage/connect-an-s3-client/) - [Object Storage limits](/docs/object-storage/limits/) - [Troubleshoot Object Storage](/docs/object-storage/troubleshooting/) ## With the API Each request takes the service ID and the bucket's `id` from [List buckets](/docs/object-storage/buckets/#list-buckets). A bucket that is not on the service answers `404` with `Bucket not found`. ### List objects [`GET /api/v1/client/object-storage/{service_id}/buckets/{bucket_id}/objects`](/docs/api/reference/client/object-storage/#op-get-api-v1-client-object-storage-service-id-buckets-bucket-id-objects) lists one page of a bucket. It takes these query parameters: `prefix` : Lists only keys that start with it, up to 1024 characters. Give a folder with its `/`, such as `photos/`. `max_keys` : How many entries a page holds, from 1 to 1000. The default is 200. `token` : The `next_token` of the previous page, to read the next one. `flat` : `true` lists every key under the prefix, with no folders. The default, `false`, groups keys into folders at each `/`. ```bash curl "https://api.coritan.com/api/v1/client/object-storage/1207/buckets/31/objects?prefix=photos/&max_keys=100" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "items": [ {"key": "photos/2026/", "size": null, "last_modified": null, "etag": null, "is_prefix": true}, {"key": "photos/beach.jpg", "size": 2483027, "last_modified": "2026-09-20T14:02:11+00:00", "etag": "9b2cf535f27731c974343645a3985328", "is_prefix": false} ], "prefix": "photos/", "next_token": null } ``` Each page lists its folders first, with `is_prefix` set to `true` and no size. `size` is in bytes. `next_token` is `null` on the last page. ### Presign an object [`POST /api/v1/client/object-storage/{service_id}/buckets/{bucket_id}/objects/presign`](/docs/api/reference/client/object-storage/#op-post-api-v1-client-object-storage-service-id-buckets-bucket-id-objects-presign) returns a presigned URL for one object. The body takes: `key` : Required. The object's key, up to 1024 bytes. A key that ends in `/` works only with `put`, which is how the browser creates a folder. `op` : Required. `get` to download, `put` to upload, or `delete` to delete. `expires` : How long the link lasts, in seconds, from 60 to 86400. We shorten anything above 3600 to 3600. The default is 900. `content_type` : For `put`, the object's content type, such as `application/pdf`, up to 255 characters. `download` : For `get`, `true` makes browsers save the file under the last part of its key instead of opening it. ```bash curl -X POST https://api.coritan.com/api/v1/client/object-storage/1207/buckets/31/objects/presign \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"key": "reports/2026-09.pdf", "op": "get", "expires": 3600, "download": true}' ``` ```json { "url": "https://s3.fra.coritan.com:7337/u7-assets/reports/2026-09.pdf?response-content-disposition=attachment%3B%20filename%3D%222026-09.pdf%22&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Date=20260926T100000Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=...", "method": "GET", "headers": {}, "expires_in": 3600, "expires_at": "2026-09-26T11:00:00+00:00" } ``` Send a request with `method` to `url` before `expires_at`, with every header in `headers`. `expires_in` is the lifetime you got, after any shortening. To upload, ask for `put` and send the file as the request body: ```bash curl -X PUT --upload-file 2026-09.pdf \ -H "Content-Type: application/pdf" \ "https://s3.fra.coritan.com:7337/u7-assets/reports/2026-09.pdf?X-Amz-Algorithm=..." ``` When you send `content_type`, `headers` holds `Content-Type` with that value, and the upload must carry it unchanged. The errors: `400` `An object key is required` for an empty key or a key that ends in `/` with `get` or `delete`, and `400` `Object keys are at most 1024 bytes`. ### Delete objects [`DELETE /api/v1/client/object-storage/{service_id}/buckets/{bucket_id}/objects`](/docs/api/reference/client/object-storage/#op-delete-api-v1-client-object-storage-service-id-buckets-bucket-id-objects) deletes up to 1000 objects by key. It does not expand folders, so list the keys under a folder with `flat=true` first. ```bash curl -X DELETE https://api.coritan.com/api/v1/client/object-storage/1207/buckets/31/objects \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"keys": ["photos/beach.jpg", "photos/2026/"]}' ``` ```json {"deleted": 2, "errors": []} ``` `deleted` counts the keys that storage did not refuse, including keys that did not exist. `errors` lists each refused key with its `key`, `code` and `message`. ## API - `GET /api/v1/client/object-storage/{service_id}/buckets/{bucket_id}/objects`: One page of the bucket under prefix, folders first (https://www.coritan.com/docs/api/reference/client/object-storage/#op-get-api-v1-client-object-storage-service-id-buckets-bucket-id-objects) - `DELETE /api/v1/client/object-storage/{service_id}/buckets/{bucket_id}/objects`: Delete named objects (https://www.coritan.com/docs/api/reference/client/object-storage/#op-delete-api-v1-client-object-storage-service-id-buckets-bucket-id-objects) - `POST /api/v1/client/object-storage/{service_id}/buckets/{bucket_id}/objects/presign`: A short-lived URL the browser uses directly for one GET, PUT or DELETE (https://www.coritan.com/docs/api/reference/client/object-storage/#op-post-api-v1-client-object-storage-service-id-buckets-bucket-id-objects-presign) # Create and revoke access keys > Create an access key that signs S3 requests for one bucket or all of them, read-only or read-write, and revoke it when done. Source: https://www.coritan.com/docs/object-storage/access-keys/ In the dashboard: - /dashboard/storage/…/keys: https://www.coritan.com/dashboard/storage An *access key* is the pair of values an S3 client signs its requests with: an *access key ID*, such as `AKIAEXAMPLE0000000000000`, and a *secret key*. You create keys on the service's **Access keys** tab. Each key is read-only or read-write, and works on one bucket or on every bucket on your account. ## Before you begin - Sign in to the [dashboard](https://www.coritan.com/dashboard/storage) and open the service. - The service must be `active` to create a key. You can revoke a key in any status. - To limit a key to one bucket, create the bucket first ([Create a bucket](/docs/object-storage/buckets/#create-a-bucket)). ## What a key can do A key has one of two sets of permissions: **Read and write** : Lists, downloads, uploads and deletes objects. **Read only** : Lists and downloads objects. It cannot change anything. No key can create or delete a bucket. Do that on the **Buckets** tab. A key also has a scope: **All buckets on this account** : The key works on every bucket whose name starts with your prefix, such as `u7-`. That covers the buckets of all your Object Storage services, including buckets you create later. One bucket : The key works on that bucket only, and every other bucket refuses it. The list offers the buckets of the service you create the key on. A key works at the endpoint of every region. Point the client at the endpoint of the region that holds the bucket. While a service is suspended, its keys stop working. They work again when the service is active. ## Create a key 1. Open the service and select the **Access keys** tab. 2. Select **Create key…**. 3. In **Label**, type a name that tells you where the key is used, such as `ci deploy`. A label has up to 64 characters: letters, digits, spaces, dots, hyphens and underscores, starting with a letter or a digit. Each label on a service is different. 4. Under **Permissions**, choose **Read and write** or **Read only**. 5. Under **Scope**, keep **All buckets on this account** or choose one bucket. 6. Select **Create key**. ## Save the secret key The dialog now shows the new key, under **Save the secret key now**. This is the only time the dashboard or the API shows the secret key. **Access key ID** and **Secret key** : The two values your client signs with. The secret key is hidden at first. Select the eye icon beside it to show it, or copy it without showing it. **Endpoint** and **Region** : The endpoint and region code of the service's home region. For a key limited to a bucket in another region, use the endpoint of that bucket's region instead. **Scope** and **Permissions** : What you chose, such as `All buckets (u7-*)` and `Read and write`. To copy the values: - **Copy all** copies every field, the secret key included, as lines of text. - The copy button beside each field copies that value. - The two code blocks hold `~/.aws/credentials` and `~/.aws/config` entries for a profile called `coritan`, ready to paste ([Connect an S3 client](/docs/object-storage/connect-an-s3-client/#aws-cli)). Store the secret key in a password manager or in your deployment's secret store, then select **I have saved it**. > [!IMPORTANT] > A new key takes up to a couple of minutes to reach storage in every region. Until then, requests signed with it are refused, usually with `InvalidAccessKeyId`. ## Read the key list The **Access keys** card says how many keys the service has, newest first. **Key** : The label, and the access key ID with a button to copy it. **Scope** : **All buckets**, or the name of the one bucket the key works on. **Permissions** : **Read and write** or **Read only**. **Created** : The date you created the key. **Last used** : We do not record when a key is used, so this column shows `Never`. A key marked **Inactive** does not work. That happens while the service is suspended, and after you delete the one bucket the key was limited to. A key limited to a deleted bucket leaves the list 7 days after the bucket. ## Revoke a key > [!WARNING] > You cannot undo a revocation. Anything that signs requests with the key is refused within a couple of minutes. 1. Open the key's menu and select **Revoke key…**. 2. Select **Revoke key** to confirm. A message confirms it, such as `Access key "ci deploy" revoked.` The key leaves the list at once, and storage refuses it within a couple of minutes. Presigned URLs that a client signed with the key stop working too. Presigned URLs from the API and the dashboard's own uploads and downloads do not use your keys, so revoking has no effect on them. To replace a key without a break in service, create the new key, wait a couple of minutes, move your applications to it, and then revoke the old key. Once the old key is revoked, you can give its label to a new key. ## Result A new key signs requests within a couple of minutes, for the buckets and permissions you chose. [Connect an S3 client](/docs/object-storage/connect-an-s3-client/) shows how to use it. A revoked key is refused everywhere within the same time. ## Troubleshooting `A key labelled ci deploy already exists on this service` : Choose another label, or revoke the key that has it first. `Labels use letters, digits, spaces, dots, hyphens and underscores` : The label holds another character, or starts with a dot, hyphen or underscore. Change it. A new key is refused with `InvalidAccessKeyId` : The key has not reached storage yet. Wait a couple of minutes and try again. If it is still refused, check that you copied the whole access key ID, then [contact support](/docs/support/conversations/). A request is refused with `AccessDenied` : The key's scope or permissions do not cover the request. A read-only key cannot upload or delete, a key limited to one bucket cannot reach another, and no key can create or delete a bucket. **Create key…** is missing : The service is not `active`. The tab says `Keys can be created once the service is active.`, or `Keys are turned off while the service is suspended.` You lost the secret key : We cannot show it again. Create a new key, move your applications to it, and revoke the old one. ## Related - [Connect an S3 client](/docs/object-storage/connect-an-s3-client/) - [Create and delete buckets](/docs/object-storage/buckets/) - [Object Storage limits](/docs/object-storage/limits/) - [Troubleshoot Object Storage](/docs/object-storage/troubleshooting/) ## With the API Each request takes the service ID. A service that is not Object Storage on your account answers `404` with `Object storage service not found`. ### List keys [`GET /api/v1/client/object-storage/{service_id}/keys`](/docs/api/reference/client/object-storage/#op-get-api-v1-client-object-storage-service-id-keys) lists the service's keys, newest first, without their secrets: ```bash curl https://api.coritan.com/api/v1/client/object-storage/1207/keys \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "items": [ { "id": 12, "access_key_id": "AKIAEXAMPLE0000000000000", "label": "ci deploy", "bucket_id": 31, "bucket_name": "u7-assets", "scope": "bucket", "mode": "read", "actions": ["Read", "List"], "is_active": true, "last_used_at": null, "created_at": "2026-09-26T10:05:00" } ], "total": 1 } ``` `scope` is `bucket` for a key limited to one bucket, named by `bucket_id` and `bucket_name`, and `all` for a key on every bucket, where both are `null`. `mode` is `read` or `read_write`. `actions` lists the S3 permissions behind the mode: `Read` and `List` for a read-only key, and `Read`, `Write`, `List` and `Tagging` for a read-write key. `is_active` is `false` while the key is turned off. `last_used_at` is always `null`. ### Create a key through the API [`POST /api/v1/client/object-storage/{service_id}/keys`](/docs/api/reference/client/object-storage/#op-post-api-v1-client-object-storage-service-id-keys) takes: `label` : Required. 1–64 characters, as in the dashboard. `mode` : `read` or `read_write`. The default is `read_write`. `bucket_id` : Optional. The `id` of one of the service's buckets, to limit the key to it. Without it, the key works on every bucket on your account. ```bash curl -X POST https://api.coritan.com/api/v1/client/object-storage/1207/keys \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"label": "ci deploy", "mode": "read", "bucket_id": 31}' ``` It answers `201` with the key in the shape above and four more fields: ```json { "id": 12, "access_key_id": "AKIAEXAMPLE0000000000000", "label": "ci deploy", "bucket_id": 31, "bucket_name": "u7-assets", "scope": "bucket", "mode": "read", "actions": ["Read", "List"], "is_active": true, "last_used_at": null, "created_at": "2026-09-26T10:05:00", "secret_key": "ExampleSecretKey000000000000000000000000000", "endpoint": "https://s3.fra.coritan.com:7337", "region": "fra", "activation_note": "The gateways are handed the new key within a couple of minutes; a request signed with it before then is refused." } ``` `secret_key` appears in this response and never again. `endpoint` and `region` are the service's home region, whatever bucket the key is limited to. `activation_note` repeats that the key needs a couple of minutes before it works. | Status | `detail` | Cause | | --- | --- | --- | | `404` | `Bucket not found` | `bucket_id` is not one of the service's buckets. | | `409` | `This service is suspended; it can be changed once it is active` | The service is not `active`. The message names its status. | | `409` | `A key labelled ci deploy already exists on this service` | Another key on the service has that label. | | `422` | `A label is required` | The label holds only spaces. | | `422` | `Labels use letters, digits, spaces, dots, hyphens and underscores` | The label holds another character, or does not start with a letter or digit. | ### Revoke a key through the API [`DELETE /api/v1/client/object-storage/{service_id}/keys/{key_id}`](/docs/api/reference/client/object-storage/#op-delete-api-v1-client-object-storage-service-id-keys-key-id) revokes a key, in any status of the service. `key_id` is the key's `id`: ```bash curl -X DELETE https://api.coritan.com/api/v1/client/object-storage/1207/keys/12 \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json {"ok": true, "access_key_id": "AKIAEXAMPLE0000000000000"} ``` A key that is not on the service answers `404` with `Access key not found`. ## API - `GET /api/v1/client/object-storage/{service_id}/keys`: List keys (https://www.coritan.com/docs/api/reference/client/object-storage/#op-get-api-v1-client-object-storage-service-id-keys) - `POST /api/v1/client/object-storage/{service_id}/keys`: Issue a key (https://www.coritan.com/docs/api/reference/client/object-storage/#op-post-api-v1-client-object-storage-service-id-keys) - `DELETE /api/v1/client/object-storage/{service_id}/keys/{key_id}`: Revoke key (https://www.coritan.com/docs/api/reference/client/object-storage/#op-delete-api-v1-client-object-storage-service-id-keys-key-id) # Connect an S3 client > Point the AWS CLI, rclone, s3cmd or an AWS SDK at your buckets with the endpoint, region and an access key. Source: https://www.coritan.com/docs/object-storage/connect-an-s3-client/ In the dashboard: - /dashboard/storage/…/overview: https://www.coritan.com/dashboard/storage Any S3 client works with Object Storage once it has four settings: our endpoint, a region name, path-style addressing and an access key. This page explains each setting, then gives working setups for the AWS CLI, rclone, s3cmd and the AWS SDKs. ## Before you begin - [Create an access key](/docs/object-storage/access-keys/) and keep its access key ID and secret key at hand. - Find the region that holds your bucket. The **Region** column of the **Buckets** tab gives its code, and the **Endpoints** card on the **Overview** tab gives the endpoint of each region the service uses. - Allow outgoing HTTPS on TCP port 7337 in any firewall between your client and the internet. ## The settings | Setting | Value | | --- | --- | | Endpoint | `https://s3..coritan.com:7337`, such as `https://s3.fra.coritan.com:7337` | | Region | Any name. Use the region code, such as `fra`. | | Addressing style | Path style | | Signature | AWS Signature Version 4 | | Access key ID and secret key | From the **Access keys** tab | Endpoint : Each region has its own endpoint, and a bucket answers only at the endpoint of its region. Another region's endpoint answers `NoSuchBucket` for it. Keep the port `7337` in the endpoint. Without it, a client connects to port 443, which does not serve Object Storage. Region : We accept any region name in the signature. A tool that insists on a region works with the region code, such as `fra`, or with `us-east-1`. Path style : The bucket name goes in the path, as in `https://s3.fra.coritan.com:7337/u7-assets/photo.jpg`. Hostnames made from a bucket name, such as `u7-assets.s3.fra.coritan.com`, do not exist, so a client set to virtual-hosted style cannot connect. The examples below use the `fra` endpoint, the bucket `u7-assets`, and the placeholders `YOUR_ACCESS_KEY_ID` and `YOUR_SECRET_KEY`. Replace them with your own values. Name the bucket in every command: an access key cannot create or delete buckets, so commands such as `aws s3 mb` are refused. ## AWS CLI Keep the key in a named profile, so the AWS CLI finds the endpoint on its own. When you create a key, the dialog shows these two blocks with your values filled in. Add the key to `~/.aws/credentials`: ```ini [coritan] aws_access_key_id = YOUR_ACCESS_KEY_ID aws_secret_access_key = YOUR_SECRET_KEY ``` Add the endpoint to `~/.aws/config`: ```ini [profile coritan] region = fra endpoint_url = https://s3.fra.coritan.com:7337 s3 = addressing_style = path ``` Then name the profile in each command: ```bash aws --profile coritan s3 ls s3://u7-assets/ aws --profile coritan s3 cp ./report.pdf s3://u7-assets/reports/report.pdf aws --profile coritan s3 sync ./site/ s3://u7-assets/site/ ``` An older AWS CLI ignores `endpoint_url` in a profile and sends every request to Amazon. Upgrade the AWS CLI, or add `--endpoint-url https://s3.fra.coritan.com:7337` to each command. With a custom endpoint, the AWS CLI uses path style on its own. The **Connect a client** card on the **Overview** tab uses `aws configure set`, which writes the key into your default profile and replaces any key that is already there. Use the named profile above to keep your other credentials. ## rclone Create a remote called `coritan`: ```bash rclone config create coritan s3 provider=Other \ endpoint=https://s3.fra.coritan.com:7337 region=fra \ force_path_style=true no_check_bucket=true \ access_key_id=YOUR_ACCESS_KEY_ID secret_access_key=YOUR_SECRET_KEY ``` The command writes this section to your rclone configuration file, which `rclone config file` locates: ```ini [coritan] type = s3 provider = Other endpoint = https://s3.fra.coritan.com:7337 region = fra force_path_style = true no_check_bucket = true access_key_id = YOUR_ACCESS_KEY_ID secret_access_key = YOUR_SECRET_KEY ``` `no_check_bucket = true` stops rclone from trying to create the bucket before it uploads, which an access key is not allowed to do. ```bash rclone ls coritan:u7-assets rclone copy ./photos coritan:u7-assets/photos rclone sync ./site coritan:u7-assets/site ``` `rclone sync` makes the destination match the source, so it deletes objects that the source does not have. ## s3cmd For a single command, pass the settings as options: ```bash s3cmd --access_key=YOUR_ACCESS_KEY_ID --secret_key=YOUR_SECRET_KEY \ --host=s3.fra.coritan.com:7337 --host-bucket=s3.fra.coritan.com:7337 \ --ssl ls s3://u7-assets/ ``` To keep them, put them in `~/.s3cfg`: ```ini [default] access_key = YOUR_ACCESS_KEY_ID secret_key = YOUR_SECRET_KEY host_base = s3.fra.coritan.com:7337 host_bucket = s3.fra.coritan.com:7337 use_https = True signature_v2 = False ``` `host_bucket` holds the endpoint's host and port with no `%(bucket)s` in it, which makes s3cmd use path style. ```bash s3cmd ls s3://u7-assets/ s3cmd put ./report.pdf s3://u7-assets/reports/ s3cmd sync ./site/ s3://u7-assets/site/ ``` ## AWS SDK for Python With boto3, pass the endpoint, a region and path style when you create the client: ```python import boto3 from botocore.config import Config s3 = boto3.client( "s3", endpoint_url="https://s3.fra.coritan.com:7337", region_name="fra", aws_access_key_id="YOUR_ACCESS_KEY_ID", aws_secret_access_key="YOUR_SECRET_KEY", config=Config(signature_version="s3v4", s3={"addressing_style": "path"}), ) s3.upload_file("report.pdf", "u7-assets", "reports/report.pdf") listing = s3.list_objects_v2(Bucket="u7-assets", Prefix="reports/") for item in listing.get("Contents", []): print(item["Key"], item["Size"]) url = s3.generate_presigned_url( "get_object", Params={"Bucket": "u7-assets", "Key": "reports/report.pdf"}, ExpiresIn=3600, ) print(url) ``` `upload_file` sends a large file in parts on its own. Keep the secret key out of your code: leave out the two key arguments, and boto3 reads the key from the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables. `boto3.Session(profile_name="coritan").client("s3", endpoint_url="https://s3.fra.coritan.com:7337")` uses the AWS CLI profile instead. ## Other AWS SDKs Every AWS SDK takes the same settings. Only the name of the path style option differs: - AWS SDK for JavaScript v3: `forcePathStyle: true` in the `S3Client` options. - AWS SDK for Go v2: `o.UsePathStyle = true` in the options function you pass to `s3.NewFromConfig`. ```javascript import { S3Client } from "@aws-sdk/client-s3"; const s3 = new S3Client({ endpoint: "https://s3.fra.coritan.com:7337", region: "fra", forcePathStyle: true, credentials: { accessKeyId: "YOUR_ACCESS_KEY_ID", secretAccessKey: "YOUR_SECRET_KEY" }, }); ``` ## Result The client lists, uploads, downloads and deletes objects within the key's scope and permissions. When a request fails, the error names the reason, and [Troubleshoot Object Storage](/docs/object-storage/troubleshooting/) explains the common ones. ## Troubleshooting The client cannot resolve, or cannot verify a certificate for, a host such as `u7-assets.s3.fra.coritan.com` : The client uses virtual-hosted style. Turn on path style, as each setup above does. The client reports a certificate error for the endpoint's own host, such as `s3.fra.coritan.com` : The endpoint has no port, so the client connects to port 443, which serves other Coritan products. Add `:7337` to the endpoint. The connection times out : A firewall between your client and us blocks port 7337. Run `curl -I https://s3.fra.coritan.com:7337`. Any HTTP status in the answer, even `403`, means your network reaches us, so check the endpoint in the client. No answer means something on your network blocks the port. `InvalidAccessKeyId` : The key is new and has not reached storage yet, so wait a couple of minutes. Otherwise, the key was revoked, the access key ID is wrong, or the client sent the request to Amazon because it has no endpoint. `SignatureDoesNotMatch` : The secret key is wrong, or only part of it was copied. Paste it again from where you saved it. If you lost it, create a new key. The same error appears when the client sends the request to an address other than the endpoint, such as an IP address, or through a proxy that changes the `Host` header. Use the endpoint exactly as the dashboard gives it, port included. `NoSuchBucket` : The bucket is in another region, or the name lacks your prefix. Use the endpoint of the bucket's region and the full name, such as `u7-assets`. `AccessDenied` : The key's scope or permissions do not cover the request. A read-only key cannot upload or delete, a key limited to one bucket cannot reach another, and no key can create a bucket. With rclone, set `no_check_bucket = true`. ## Related - [Create and revoke access keys](/docs/object-storage/access-keys/) - [Upload, download and delete objects](/docs/object-storage/objects/) - [Object Storage limits](/docs/object-storage/limits/) - [Troubleshoot Object Storage](/docs/object-storage/troubleshooting/) ## With the API `GET /api/v1/client/object-storage/{service_id}` returns the service's endpoints in `endpoints`, one for each region it uses ([Object Storage](/docs/object-storage/#with-the-api)). Each bucket in [List buckets](/docs/object-storage/buckets/#list-buckets) carries the `endpoint` of its region and its `url`, which is the endpoint with the bucket's name added. # How Object Storage is billed > What your plan's storage allowance covers, how we measure what your buckets hold, and what happens when you go over or change plan. Source: https://www.coritan.com/docs/object-storage/usage-and-billing/ In the dashboard: - /dashboard/storage/…/overview: https://www.coritan.com/dashboard/storage - /dashboard/storage/…/billing: https://www.coritan.com/dashboard/storage Each Object Storage service is on a plan. The plan's price covers a storage *allowance*, and you pay that price on the billing cycle you chose when you ordered. We measure what your buckets hold every hour, and the service's **Overview** tab shows it against the allowance. ## What the plan's price covers The allowance is the storage included in the price. The order page shows it on each plan, followed by the plan's rate for storage above it. On the service's **Overview** tab, the **Service** card shows it as **Included**. A GB is 1024³ bytes. - The allowance covers all the service's buckets together, in every region they are in. - Buckets and access keys have no price of their own. A service holds up to 25 buckets. - A plan without an allowance shows `Unmetered` under **Included**, and the **Storage** figure says `No allowance on this plan`. Nothing on such a plan counts as above the plan. ## How we measure your buckets Every hour, we list every object in each of the service's buckets and record their total size and their number. We measure only what the buckets hold. Requests, uploads and downloads are not counted. - The service's figure adds up the buckets it holds now. It appears as **Storage** on the **Overview** tab and in the list of services, and as `used_bytes` in the API. - Every size and object count comes from the last measurement, so the figures trail uploads and deletions by up to an hour. - A bucket you delete leaves the figure at once. Objects you delete leave it at the next measurement. - A new bucket counts from its first measurement, up to an hour after you create it. The **Stored over time** chart on the **Overview** tab shows the measurements of the last `7d`, `30d` or `90d`, added up across the service's buckets. It includes a bucket you deleted for the hours before the delete, until we remove that bucket's history 7 days later. `No measurements yet` means no bucket was measured in the window you chose ([The usage chart is empty](/docs/object-storage/troubleshooting/#the-usage-chart-is-empty)). ## When your buckets hold more than the allowance Uploads go on working above the allowance. We do not refuse a request or delete an object because the service is above it. Each hour, after the measurement, we record how many GB the service holds above its allowance, or zero when it is within it. We keep this record for every active or suspended service that has at least one bucket. The plan sets a rate per *GB-month* for storage above the allowance. A GB-month is one GB held for a whole month, so 10 GB above the allowance for half a month makes 5 GB-months. The **Service** card shows the rate as **Above the plan**. While the last measurement is above the allowance: - A warning at the top of the **Overview** tab gives the amount, such as `12.5 GB above the plan`, and the plan's rate per GB-month. - On the **Stored over time** chart, the bars above the dashed line that marks the allowance are red. - `overage_gb` in the API is above zero. Each invoice lists its charges line by line, as [Read an invoice](/docs/billing/invoices/#read-an-invoice) describes. ## Bring the storage back within the allowance The warning suggests two ways: - Delete what you no longer need: objects in the object browser ([Delete objects and folders](/docs/object-storage/objects/#delete-objects-and-folders)), or whole buckets ([Delete a bucket](/docs/object-storage/buckets/#delete-a-bucket)). - Move to a larger plan, as the next section describes. The warning's **Change plan** button opens the **Billing** tab. The warning goes once the service's figure is back within the allowance. That happens at once when you delete a bucket, and after the next measurement when you delete objects. ## What a plan change does Each Object Storage size is a product of its own, with a plan for each billing cycle it comes in. **Change plan…** on the **Billing** tab moves the service to another billing cycle of the same size, and the allowance stays the same. We prorate the switch to the day, and the dialog previews what it costs before you apply it ([Change a service's plan](/docs/billing/change-plan/)). When the size comes in one billing cycle only, the tab says `No other plan to switch to`. A larger or smaller allowance takes a new service. Order one on the plan you want, copy your objects across, then cancel the old service ([Change the plan](/docs/object-storage/order-object-storage/#change-the-plan)). You pay for both services until the old one ends. ## What the Billing tab shows The **Billing** tab shows the plan and what you pay for it. The **Current plan** card gives the **Plan**, its **Price** and the **Next renewal** date, and the **Activity** table lists setup, renewals and plan changes. The tab does not show storage, which the **Overview** tab covers. [Manage the plan on the Billing tab](/docs/object-storage/order-object-storage/#manage-the-plan-on-the-billing-tab) describes each card, including **Cancel service**. The plan renews on the date under **Next renewal**, with an invoice for its price ([How you are billed](/docs/billing/#how-you-are-billed)). ## Limits - Each service has its own allowance. Spare room on one service does not cover the buckets of another. - A plan change keeps the allowance. - We measure once an hour, so no figure is newer than the last measurement. - The API returns up to 366 days of measurements, and the chart shows up to 90. ## With the API `GET /api/v1/client/object-storage/{service_id}` returns the service with `quota_bytes`, `used_bytes`, `overage_per_gb_month` and `overage_gb`, as [Object Storage](/docs/object-storage/#with-the-api) describes. `GET /api/v1/client/object-storage/{service_id}/usage` returns the same figures with the measurements behind the chart. `days` sets the window, from 1 to 366 days, and is 30 when you leave it out. ```bash curl "https://api.coritan.com/api/v1/client/object-storage/1207/usage?days=7" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "quota_bytes": 268435456000, "used_bytes": 281857228800, "object_count": 1284, "bucket_count": 2, "overage_gb": 12.5, "days": 7, "series": [ {"measured_at": "2026-09-19T10:00:00", "bytes": 279172874240, "objects": 1270}, {"measured_at": "2026-09-19T11:00:00", "bytes": 281857228800, "objects": 1284} ] } ``` `series` has one point for each hourly measurement in the window, oldest first, in UTC. Each point adds up `bytes` and `objects` across the service's buckets. `overage_gb` is what the buckets held above `quota_bytes` at the last measurement, in GB of 1024³ bytes, and is `0` on a plan without an allowance. A service ID that is not an Object Storage service on your account answers `404` with `Object storage service not found`. ## API - `GET /api/v1/client/object-storage/{service_id}`: Get service (https://www.coritan.com/docs/api/reference/client/object-storage/#op-get-api-v1-client-object-storage-service-id) - `GET /api/v1/client/object-storage/{service_id}/usage`: Stored bytes and objects over time, as the hourly meter recorded them (https://www.coritan.com/docs/api/reference/client/object-storage/#op-get-api-v1-client-object-storage-service-id-usage) # Object Storage limits > The limits on buckets, bucket names, object keys, access keys, listings, presigned URLs and deletions in Object Storage. Source: https://www.coritan.com/docs/object-storage/limits/ This page lists every fixed limit in Object Storage, with the message you see when you reach one. The plan sets the storage allowance, and the page that covers each task explains how to work within its limits. ## Service | What | Limit | | --- | --- | | Storage | The plan's allowance, shown as **Included** on the **Overview** tab. Uploads go on working above it, and the plan sets a rate per GB-month for the storage above it. A GB is 1024³ bytes. | | Buckets | 25 on each service. Over it: `This service already has 25 buckets, which is the most one service may hold`. | | Access keys | No fixed number. | | Plan change | Keeps the allowance. For more storage included, move to a larger plan ([Change the plan](/docs/object-storage/order-object-storage/#change-the-plan)). | | Changes while the service is not `active` | You cannot create or delete a bucket or create a key: `This service is suspended; it can be changed once it is active`, with the service's status in place of `suspended`. The dashboard shows **Upload…** and **New folder…** only while the service is `active`. You can revoke a key in any status. | | Suspension | Keys stop working within a couple of minutes and work again when the service is active. The objects stay where they are. | | After the service ends | Keys stop working within a couple of minutes. We keep the objects for 7 days, then delete them with their buckets ([Cancel the service](/docs/object-storage/order-object-storage/#cancel-the-service)). | ## Bucket names | What | Limit | | --- | --- | | Prefix | Every name starts with your account namespace and a hyphen, such as `u7-`. We add it when you leave it out. | | Length | 3–63 characters, prefix included. With the prefix `u7-`, you can type up to 60. Over it: `Use at most 63 characters, including the prefix.` | | Characters | Lowercase letters, digits, hyphens and dots. We change uppercase letters to lowercase. Anything else: `Use lowercase letters, digits, hyphens and dots, and start and end with a letter or digit.` | | First and last character | A letter or a digit. | | Dots | Not next to another dot or a hyphen: `Dots and hyphens cannot be next to each other.` | | Uniqueness | One bucket with each name across your account, whichever service holds it: `A bucket named u7-assets already exists`. | | After a delete | The name stays taken for 7 days: `u7-assets was deleted recently and is still being removed. Pick another name, or wait for the removal to finish.` | | Rename or move | Not possible. Create a new bucket and copy the objects into it with an S3 client. | The dashboard checks the length, the characters and the dots as you type, and shows the message under **Name**. It shows the messages about a name that is taken after you select **Create bucket**. [Create a bucket through the API](/docs/object-storage/buckets/#create-a-bucket-through-the-api) lists the messages the API gives for each rule. ## Buckets | What | Limit | | --- | --- | | Region | One, chosen when you create the bucket. The bucket answers only at that region's endpoint. | | Objects in a bucket | No fixed number. | | Public access | None. Every request needs a signature from an access key, or a presigned URL. | | Versions | None. An upload replaces an object that has the same key, and a deleted object cannot be restored. | | Custom domains | None. Buckets answer only at the regional endpoints. | | Deleting a bucket that holds objects | Needs the tick box in the dashboard, or `force=true` through the API. Otherwise: `u7-assets is not empty. Delete it with force to destroy its contents as well.` | ## Objects | What | Limit | | --- | --- | | Object key | Up to 1,024 bytes in UTF-8 for uploads in the dashboard and presigned URLs from the API. Over it: `Object keys are at most 1024 bytes`. | | Object size | The dashboard and the API check no size. The dashboard sends each file in one request, so upload files of several gigabytes with an S3 client, which sends them in parts. | | Uploads in the dashboard | Files only, one request each. Upload a folder with its contents from an S3 client. | | Downloads in the dashboard | One object at a time. | | Folder names in the dashboard | No backslash: `Enter a folder name without backslashes.` We remove a `/` at the start or the end. | ## Access keys | What | Limit | | --- | --- | | Keys on a service | No fixed number. | | Label | 1–64 characters: letters, digits, spaces, dots, hyphens and underscores, starting with a letter or a digit. Otherwise: `Labels use letters, digits, spaces, dots, hyphens and underscores`. | | Label on a service | Each key on a service has a label of its own, and labels that differ only in upper and lower case count as the same: `A key labelled ci deploy already exists on this service`. | | Access key ID | 24 characters, starting with `AKIA`. | | Secret key | 43 characters, shown once when you create the key. | | Permissions | **Read only** or **Read and write**. No key can create or delete a bucket. | | Scope | One bucket of the service, or **All buckets on this account**. | | Regions | A key works at every regional endpoint. | | Time to take effect | Up to a couple of minutes after you create or revoke a key. | [What a key can do](/docs/object-storage/access-keys/#what-a-key-can-do) explains the permissions and scopes. ## Presigned URLs | What | Limit | | --- | --- | | Lifetime of a link from the API | 60–3600 seconds, 900 by default. We shorten a request for up to 86400 seconds to 3600. Below 60 or above 86400, the API answers `422`. | | What a link does | One operation on one object: `get`, `put` or `delete`. | | Uses | Any number, until the link expires. | | Object key | Up to 1,024 bytes: `Object keys are at most 1024 bytes`. | | Content type | Up to 255 characters, for `put`. | | Links from an S3 client | Signed with your key. They stop working when they expire or when you revoke the key. | [Share a file with a presigned URL](/docs/object-storage/objects/#share-a-file-with-a-presigned-url) shows both ways to create one. ## Listing and deleting | What | Limit | | --- | --- | | Entries per page in the dashboard | 200. **Load more** lists the next 200. | | Entries per page through the API | 1–1000 (`max_keys`), 200 by default. | | `prefix` through the API | Up to 1,024 characters. | | `token` through the API | Up to 2,048 characters. | | Keys in one delete request through the API | 1–1000. | | Deleting a folder in the dashboard | Up to 10,000 objects under each folder in one run. Over it: `A folder had more than the browser deletes at once; run the delete again for the rest.` | ## Usage history | What | Limit | | --- | --- | | Measurement | Every hour, for every bucket. Sizes and object counts come from the last measurement. | | History through the API | 1–366 days, 30 by default: `days` on [`GET /api/v1/client/object-storage/{service_id}/usage`](/docs/api/reference/client/object-storage/#op-get-api-v1-client-object-storage-service-id-usage). | | **Stored over time** chart | The last 7, 30 or 90 days, in at most 120 bars. | ## Connection | What | Limit | | --- | --- | | Endpoint | `https://s3..coritan.com:7337`, one for each region. | | Port | 7337. Port 443 does not serve Object Storage. | | Protocol | HTTPS with TLS 1.2 or 1.3. A plain HTTP request to port 7337 answers `400 Bad Request`. | | Addressing | Path style only. | | Signature | AWS Signature Version 4, with any region name. | | Host | The endpoint's host and port, exactly. A request sent to an IP address, or through a proxy that changes the `Host` header, fails with `SignatureDoesNotMatch`. | [Connect an S3 client](/docs/object-storage/connect-an-s3-client/#the-settings) explains each setting. ## Rate limits The Object Storage routes of the Coritan API have no budget of their own. They count towards the limits on requests from one address that [Rate limits](/docs/api/rate-limits/) describes. S3 requests go to the regional endpoint and do not count towards those limits. ## Related - [Object Storage](/docs/object-storage/) - [Create and delete buckets](/docs/object-storage/buckets/) - [Create and revoke access keys](/docs/object-storage/access-keys/) - [Troubleshoot Object Storage](/docs/object-storage/troubleshooting/) # Troubleshoot Object Storage > Fix refused S3 requests, bucket names that are not accepted, missing keys and other common Object Storage problems. Source: https://www.coritan.com/docs/object-storage/troubleshooting/ Find the symptom or the message you see, then follow the fix. Messages appear here exactly as the dashboard, the API or your S3 client shows them, and each fix links to the page that covers the task in full. ## A new key is refused Cause : A new key takes up to a couple of minutes to reach storage in every region. Until then, storage refuses requests signed with it, usually with `InvalidAccessKeyId`. Fix : Wait a couple of minutes and try again. If the key is still refused, check that the client has the whole access key ID, which starts with `AKIA` and is 24 characters long. Check too that the client sends requests to our endpoint: a client with no endpoint sends them to Amazon, which does not know the key. With the AWS CLI, add `--debug` to a command and look for `s3.fra.coritan.com:7337` in the request URL ([Connect an S3 client](/docs/object-storage/connect-an-s3-client/)). ## A key that worked is refused Cause : Storage answers `InvalidAccessKeyId` when it no longer accepts a key. That happens when you revoke the key, when the key's service is suspended or has ended, and when the key was limited to a bucket that you deleted. Each of these turns the key off within a couple of minutes. Fix : Open the service's **Access keys** tab. A revoked key is no longer in the list, and a key that is turned off shows **Inactive**. For a suspended service, pay the overdue invoice ([Failed payments and suspended services](/docs/billing/failed-payments/)), and its keys work again within a couple of minutes of the service becoming active. In the other cases, [create a new key](/docs/object-storage/access-keys/#create-a-key) and give it to the client. ## Requests fail with a signature error Cause : Storage answers `SignatureDoesNotMatch` when the signature it computes differs from the one the client sent. The secret key is wrong, or only part of it was copied. The same error appears when the client sends the request to an address other than the endpoint, such as an IP address, or through a proxy that changes the `Host` header. Storage checks the signature against the endpoint's host and port. Fix : Paste the secret key again from where you saved it, with no space or line break around it. Use the endpoint exactly as the dashboard gives it, such as `https://s3.fra.coritan.com:7337`, and send requests to it directly. If you lost the secret key, [create a new key](/docs/object-storage/access-keys/#create-a-key), because we cannot show a secret key again. ## Requests are refused with access denied Cause : Storage answers `AccessDenied` when the key's permissions or scope do not cover the request. A read-only key cannot upload or delete, a key limited to one bucket cannot reach another, and no key can create or delete a bucket. That is why storage refuses `aws s3 mb` and `aws s3 rb`, and rclone when it tries to create the bucket before an upload. Storage also refuses a request with no signature, such as a bucket's address opened in a web browser. Fix : Check the key's **Scope** and **Permissions** on the **Access keys** tab. To upload or delete, use a key with **Read and write**. For another bucket, use a key limited to that bucket, or one whose scope is **All buckets**. Create and delete buckets on the **Buckets** tab ([Create and delete buckets](/docs/object-storage/buckets/)). With rclone, set `no_check_bucket = true` in the remote ([rclone](/docs/object-storage/connect-an-s3-client/#rclone)). To share an object with someone who has no key, send a [presigned URL](/docs/object-storage/objects/#share-a-file-with-a-presigned-url). ## A bucket is not found Cause : Storage answers `NoSuchBucket` when the endpoint has no bucket with that name. The bucket lives in another region, the name lacks your prefix, or the bucket was deleted. Fix : On the **Buckets** tab, find the bucket's full name and its **Region**. Send requests to that region's endpoint, which the **Endpoints** card on the **Overview** tab lists, and use the full name, such as `u7-assets`. A bucket that none of your services lists was deleted, together with its objects. ## The client cannot find the host or reports a certificate error Cause : The client uses virtual-hosted style, which puts the bucket name in the host, such as `u7-assets.s3.fra.coritan.com`. Those hosts do not exist. A certificate error for the endpoint's own host, such as `s3.fra.coritan.com`, means the endpoint has no port, so the client connects to port 443, which serves other Coritan products. Fix : Turn on path style, as each setup in [Connect an S3 client](/docs/object-storage/connect-an-s3-client/) does, and keep `:7337` at the end of the endpoint. ## The connection times out Cause : Something between your client and us blocks outgoing connections to port 7337, such as a firewall, a proxy or a VPN. Object Storage answers only on that port. Fix : Run `curl -I https://s3.fra.coritan.com:7337` on the same machine. Any HTTP status in the answer, even `403`, means your network reaches us, so check the endpoint in the client. No answer means the port is blocked: allow outgoing HTTPS on TCP port 7337, or ask whoever runs the network to allow it. ## Every request fails with status 400 Cause : The client sends plain HTTP. Port 7337 accepts HTTPS only, and it answers a plain HTTP request with `400 Bad Request`. Fix : Start the endpoint with `https://`. In s3cmd, set `use_https = True` in `~/.s3cfg`, or pass `--ssl`. ## A presigned URL stops working Cause : Storage answers `403` for a link that has expired. A link from the API lasts 15 minutes unless you asked for longer, and 1 hour at most. A link that an S3 client signed with your key also stops working when you revoke the key. Each link works only with the method it was signed for, and an upload link signed with a content type needs the same `Content-Type` header on the upload. Fix : Create a new link ([Share a file with a presigned URL](/docs/object-storage/objects/#share-a-file-with-a-presigned-url)). Send the request with the `method` the API returned and every header in `headers`. For a file that someone needs for longer, send a new link each time, or give them a read-only key limited to the bucket. ## A bucket name is refused Cause : The name breaks a naming rule, or your account already uses it. The message under **Name** says which rule, such as `Dots and hyphens cannot be next to each other.` The message `A bucket named u7-assets already exists` means one of your services has a bucket with that name. `u7-assets was deleted recently and is still being removed. Pick another name, or wait for the removal to finish.` means you deleted a bucket with that name in the last 7 days. Fix : Change the name to follow [Bucket names](/docs/object-storage/buckets/#bucket-names), or choose another one. The name of a deleted bucket is free again 7 days after the delete. ## You cannot create another bucket Cause : The service holds 25 buckets, the most one service may hold. **Create bucket…** is greyed out, and the API answers `409` with `This service already has 25 buckets, which is the most one service may hold`. Fix : Delete a bucket you no longer need ([Delete a bucket](/docs/object-storage/buckets/#delete-a-bucket)). It stops counting as soon as you delete it. You can also order another service, which holds up to 25 buckets of its own. ## A region is not available Cause : No region can take a bucket at the moment, or the region you chose has stopped taking them. The order page says `No region has a gateway right now`, and the bucket dialog says `No region is available right now`. Creating a bucket in a region that has stopped taking them fails with `That region does not offer object storage`. An order for such a region fails during setup with the same message, and the service's status becomes `failed`. Fix : Choose another region if the list offers one. Otherwise, try again later. For a service whose status is `failed`, [contact support](/docs/support/conversations/) with its service ID. ## A bucket will not delete Cause : The bucket holds objects that the last hourly measurement did not count, so the dialog offered no box to destroy them. We refuse the delete with `u7-assets is not empty. Delete it with force to destroy its contents as well.` Fix : Wait for the next measurement and delete the bucket again with the box ticked. You can also delete the objects first ([Delete objects and folders](/docs/object-storage/objects/#delete-objects-and-folders)), or delete the bucket through the API with `force=true` ([Delete a bucket through the API](/docs/object-storage/buckets/#delete-a-bucket-through-the-api)). ## Buttons to create or change things are missing Cause : The service is not `active`. The banner above the tabs says why: **Waiting for payment**, **Setting up** or **Suspended**. Until the service is active, the dashboard hides **Create bucket…**, **Delete bucket…**, **Create key…**, **Upload…** and **New folder…**. You can still browse buckets, download objects and revoke keys. Fix : For **Waiting for payment**, select **Open invoices** and pay the first invoice. **Setting up** takes under a minute, so reload the page. For **Suspended**, pay the overdue invoice ([Failed payments and suspended services](/docs/billing/failed-payments/)). ## Uploads in the dashboard fail Cause : Your browser sends each upload straight to the region's endpoint on port 7337. The message after `Could not upload` and the file's name says what went wrong. `Could not reach the storage gateway` means your browser could not connect, usually because a firewall, proxy or VPN blocks the port. `The upload link expired or was refused` means storage answered `403`. `Upload failed` with a number means storage answered with that HTTP status. Fix : For `Could not reach the storage gateway`, try another network, or ask whoever runs yours to allow outgoing HTTPS on port 7337. For the other two, reload the page and upload the file again. Send a large file with an S3 client, which uploads it in parts ([Connect an S3 client](/docs/object-storage/connect-an-s3-client/)). ## A folder is still there after you deleted its objects Cause : The folder has an empty folder object, whose key ends in `/`. **New folder…** writes one, and so do some S3 clients. Fix : Delete the folder itself with **Delete folder…** in its menu, or tick it and delete it ([Delete objects and folders](/docs/object-storage/objects/#delete-objects-and-folders)). ## Sizes and object counts do not change Cause : We measure each bucket once an hour. The object count and size on the **Buckets** tab, in the object browser's header and on the **Overview** tab come from the last measurement. Fix : Wait for the next measurement. The **Measured** column on the **Buckets** tab says when we last measured each bucket. The list of objects comes straight from storage, and the refresh button beside the bucket's name lists the folder again. ## The usage chart is empty Cause : `No measurements yet` means we have not measured any of the service's buckets in the window you chose. A new service or bucket waits up to an hour for its first measurement, and a service with no buckets has nothing to measure. Fix : [Create a bucket](/docs/object-storage/buckets/#create-a-bucket) if the service has none, and look again after the next hourly measurement. ## The Overview tab says the service is above the plan Cause : At the last measurement, your buckets held more than the plan's allowance. The warning, such as `12.5 GB above the plan`, gives the rate for the storage above it. Fix : Delete objects or buckets you no longer need, and the warning clears after the next measurement. For a larger allowance, move to a larger plan as [Change the plan](/docs/object-storage/order-object-storage/#change-the-plan) describes. The warning's **Change plan** button opens the **Billing** tab, where a plan change keeps the same allowance. ## Last used always says Never Cause : We do not record when a key is used. The **Last used** column on the **Access keys** tab always shows `Never`, and the API always returns `last_used_at` as `null`. Fix : Nothing is wrong with the key. To keep track of where each key is used, give it a label that names the place, such as `ci deploy`. ## You lost a secret key Cause : The dashboard and the API show a secret key only once, when you create the key. Fix : Create a new key, move your applications to it, then revoke the old key ([Create and revoke access keys](/docs/object-storage/access-keys/)). ## The bucket you named in the order is missing Cause : We could not create it, or your account already had a bucket with that name, or had one that you deleted in the last 7 days. When we could not create it, the setup line in the **Activity** table on the **Billing** tab ends with `The first bucket was not created:` and a reason. When the name was taken, the line does not mention the bucket. Fix : Create the bucket on the **Buckets** tab, with another name if the name is taken ([Create a bucket](/docs/object-storage/buckets/#create-a-bucket)). ## Errors from the API `400` `An object key is required` or `Object keys are at most 1024 bytes` : The presign request has no key, a key that ends in `/` with `get` or `delete`, or a key longer than 1024 bytes ([Presign an object](/docs/object-storage/objects/#presign-an-object)). `404` `Object storage service not found` : The service ID is not an Object Storage service on your account. List your services with `GET /api/v1/client/object-storage/services` ([Object Storage](/docs/object-storage/#with-the-api)). `404` `Bucket not found` : The bucket ID is not a bucket of this service, or you deleted the bucket. Take the `id` from [List buckets](/docs/object-storage/buckets/#list-buckets). `404` `Access key not found` : The key ID is not a key of this service, or you revoked the key. `409` `This service is pending; it can be changed once it is active` : The service is not `active`, and the message names its status. See [Buttons to create or change things are missing](#buttons-to-create-or-change-things-are-missing). `422` : A value in the request breaks a rule, and `detail` says which one, such as `Bucket names cannot have a dot next to another dot or a hyphen`. [Object Storage limits](/docs/object-storage/limits/) lists every rule. `502` with a message that starts `Gateway refused`, `Could not list`, `Could not sign the request` or `Could not delete objects` : The region's storage refused or failed the request. Try again in a minute. If it keeps happening, [contact support](/docs/support/conversations/) with the whole message. `503` `Object storage is not enabled on this platform` : Storage in the bucket's region cannot take requests right now. Try again later, and [contact support](/docs/support/conversations/) if it lasts. [Errors](/docs/api/errors/) explains the shape of every error response. ## None of these match [Contact support](/docs/support/conversations/) and include: - The **Service ID** from the service's **Billing** tab. - The bucket's name and region. - The access key ID of the key involved. Never send a secret key, to us or to anyone else. - The endpoint your client uses, and the client's name and version. - The whole error message, and when it happened. # Email > Host mailboxes on your own domain with Mail Hosting, send your applications' mail with SMTP Relay, and read mail in webmail. Source: https://www.coritan.com/docs/mail/ In the dashboard: - /solutions/mail: https://www.coritan.com/solutions/mail - /dashboard/mail: https://www.coritan.com/dashboard/mail Coritan has two mail products. *Mail Hosting* gives people mailboxes on your own domain, which they read in a mail app or in webmail. *SMTP Relay* sends the mail your software writes, such as receipts, password resets and newsletters, over SMTP or an HTTPS API, and reports what happened to each message. Both run in the region you choose when you order, and both use DNS records on your domain to prove the mail is yours. You can order more than one of each. ## What you can do Start here: - [Order Mail Hosting or SMTP Relay](/docs/mail/order-a-mail-service/) - [Mail DNS records](/docs/mail/dns-records/): what each record is for and how to publish it - [How the DMARC stage ramp works](/docs/mail/dmarc/) Mail Hosting: - [How Mail Hosting works](/docs/mail/mail-hosting/) - [Add a domain to Mail Hosting](/docs/mail/mail-hosting/add-a-domain/) - [Create and manage mailboxes](/docs/mail/mail-hosting/mailboxes/) - [Secure a mailbox](/docs/mail/mail-hosting/mailbox-security/) with two-factor sign-in and app passwords - [Forward addresses with aliases](/docs/mail/mail-hosting/aliases/) - [Connect a mail app](/docs/mail/mail-hosting/connect-a-mail-app/) - [Move existing mail into a mailbox](/docs/mail/mail-hosting/move-mail-in/) - [Change Mail Hosting settings](/docs/mail/mail-hosting/settings/) SMTP Relay: - [How SMTP Relay works](/docs/mail/smtp-relay/) - [Add a sending domain](/docs/mail/smtp-relay/add-a-sending-domain/) - [Send over SMTP with credentials](/docs/mail/smtp-relay/credentials/) - [Create and revoke send API keys](/docs/mail/smtp-relay/api-keys/) and [send email over HTTPS](/docs/mail/smtp-relay/send-with-the-api/) - [Transactional and marketing mail](/docs/mail/smtp-relay/message-categories/) - [Receive delivery events with webhooks](/docs/mail/smtp-relay/webhooks/), [look up message events](/docs/mail/smtp-relay/events/) and [manage the suppression list](/docs/mail/smtp-relay/suppressions/) - [How sending limits work](/docs/mail/smtp-relay/sending-limits/) and [request a higher hourly limit](/docs/mail/smtp-relay/request-a-higher-limit/) - [Sending reputation and deliverability](/docs/mail/smtp-relay/deliverability/) Webmail: - [How webmail works](/docs/mail/webmail/), from [getting a free coritan.gg mailbox](/docs/mail/webmail/claim-a-mailbox/) to [filters](/docs/mail/webmail/labels-and-filters/) and [keyboard shortcuts](/docs/mail/webmail/keyboard-shortcuts/) When something goes wrong, see [Troubleshoot mail](/docs/mail/troubleshooting/). ## Find your mail services In the dashboard, go to **Email**. The list shows every Mail Hosting and SMTP Relay service on your account, with its **Domain**, **Product**, **Status**, **Plan** and **Since** date. Filter it by status, or search by domain or plan. Select a row to open the service's panel. The **Plan** column says what the service holds: a count of mailboxes for Mail Hosting, and the monthly email allowance for SMTP Relay. A service that is still being set up reads `Not set up yet`. To order, select **Order Mail Hosting** or **Order SMTP Relay** at the top of the list. Each opens the product's order page. The public [Mail page](https://www.coritan.com/solutions/mail) lists the current plans before you sign in. ## Billing Each service is billed for its plan and renews every billing cycle until you cancel it. SMTP Relay's order page also offers a free plan. [How Mail Hosting and SMTP Relay are billed](/docs/mail/billing/) covers plan changes, the monthly sending allowance and cancelling. ## Limits that apply to every mail service - A service lives in the region you chose when you ordered it, and its mail is stored there. - Prove you own a domain and publish its records before you send from it. A domain counts as verified once its ownership, SPF and DKIM checks pass, and for Mail Hosting its MX check too. See [Mail DNS records](/docs/mail/dns-records/). - A domain can be on one mail service at a time. To use a domain on both products, give the relay a sending subdomain, such as `send.example.com`. - The plan sets how many domains, mailboxes or SMTP credentials the service may hold. The panel shows each count against its limit. - Coritan pauses a service's sending when its bounce or complaint rate crosses the platform's limit. See [Sending reputation and deliverability](/docs/mail/smtp-relay/deliverability/). ## With the API List your mail services: ```bash curl https://api.coritan.com/api/v1/client/mail/services \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` `GET /client/smtp-relay/services` returns the same list. Both answer `{"items": [...], "total": n}` with one item for every Mail Hosting and SMTP Relay service on your account: ```json { "items": [ { "service_id": 4812, "tenant_id": 97, "internal": false, "module_name": "smtp_relay", "product_name": "Relay 50K", "status": "active", "hostname": "send.example.com", "tenant": { "...": "..." }, "created_at": "2026-09-01T10:12:00" } ], "total": 1 } ``` `module_name` is `mail` for Mail Hosting and `smtp_relay` for SMTP Relay. Use `service_id` as `{service_id}` in every other mail request. The two prefixes share one set of routes, so `/client/mail/{service_id}/...` and `/client/smtp-relay/{service_id}/...` reach the same service. An operation that belongs to the other product answers `400` or `404` with a message that says so, such as `Mailboxes are only available on Mail Hosting services`. An item with `"internal": true` and `"service_id": null` is an [internal mail tenant](/docs/mail/internal-tenants/). Reach it through `/client/platform-mail/{tenant_id}/...` instead. A service that is still being set up answers `409` `This mail service is still provisioning`. See [Authentication](/docs/api/authentication/) for tokens. ## API - `GET /api/v1/client/mail/services`: List services (https://www.coritan.com/docs/api/reference/client/mail/mail/#op-get-api-v1-client-mail-services) - `GET /api/v1/client/smtp-relay/services`: List services (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay/#op-get-api-v1-client-smtp-relay-services) # Order Mail Hosting or SMTP Relay > Choose Mail Hosting for mailboxes or SMTP Relay for application mail, pick a plan and a domain, and pay for the order on the page it lands on. Source: https://www.coritan.com/docs/mail/order-a-mail-service/ In the dashboard: - /dashboard/mail: https://www.coritan.com/dashboard/mail - /dashboard/order/mail-hosting: https://www.coritan.com/dashboard/order/mail-hosting - /dashboard/order/smtp-relay: https://www.coritan.com/dashboard/order/smtp-relay - /dashboard/order/relay-dedicated-ip: https://www.coritan.com/dashboard/order/relay-dedicated-ip Order Mail Hosting when people need mailboxes on your domain, and SMTP Relay when software needs to send mail, such as receipts, alerts and newsletters. Each product has an order page of its own, and you can hold several of each. A relay on a large enough plan can also take a dedicated sending IP, which you order for that relay. ## Before you begin - Decide which product you need. [How Mail Hosting works](/docs/mail/mail-hosting/) and [How SMTP Relay works](/docs/mail/smtp-relay/) describe each. - For a paid plan, credit on your account pays the first invoice first. Otherwise you pay by card or PayPal on the page the order lands on ([How the first payment works](/docs/get-started/order-a-service/#how-the-first-payment-works)). - Optionally, have a domain whose DNS you can change. You can also add it after the order. - To use one domain for both products, plan a sending subdomain for the relay, such as `send.example.com`. A domain can be on one mail service at a time. ## Open the order page Any of these opens the order page for Mail Hosting or SMTP Relay: - In [**Email**](https://www.coritan.com/dashboard/mail), select **Order Mail Hosting** or **Order SMTP Relay**. With no mail service yet, the same buttons are in the middle of the page. - In a mail service's panel, select **Order another** in its header. It opens the page for the same product. - On [**Order a service**](https://www.coritan.com/dashboard/order), select the **Mail Hosting** or **SMTP Relay** card under **Email**. You can also add Mail Hosting or SMTP Relay to a Cloud Compute or Container Apps order, and SMTP Relay to a Mail Hosting order ([Add products to the order](/docs/get-started/order-a-service/#add-products-to-the-order)). ## Order the service The page has numbered sections, and the summary under **Your order** sits beside them. On a phone the summary follows the sections, and **Review order** at the bottom of the screen takes you to it. 1. Under **Plan**, choose a plan. We start with the plan the catalogue recommends, or else the first one on sale. - A Mail Hosting plan shows how many mailboxes it holds, the storage each mailbox gets and how many domains it takes, such as `5 mailboxes · 10 GB each · 1 domain`. - An SMTP Relay plan shows its emails a month, its price per 1,000, whether it has a buffer over the allowance or `stops at the allowance`, its sending domains and any dedicated IPs. The free plan shows **Free** as its price. 2. Check the billing cycle beside the **Plan** heading. When the plans sell more than one cycle, select one of its buttons, such as **Monthly**. A longer term names what it saves, such as `Annually · save 20%`. Relay plans are billed monthly, so the SMTP Relay page reads `Billed monthly` there. 3. Under **Domain** (Mail Hosting) or **Sending domain** (SMTP Relay), optionally enter a domain such as `example.com` in **First domain** or **First sending domain**. Leave it empty to add domains from the panel later. - If you hold free [coritan.gg names](/docs/proxies/coritan-gg-names/), they appear beside **Your names:** under the field. Select one to use it. 4. On a Mail Hosting order, **Add-ons** offers an SMTP Relay for the mail your applications send. To add one, turn on the switch on the **SMTP Relay** card. Choose its **Plan** when the card lists more than one, and optionally enter a **Sending domain**. Only paid relay plans are offered here: order the free plan on the SMTP Relay page. 5. Check the summary. It lists the plan with its billing cycle and the domain, with **Change** beside each to go back to its section. Under **Billed** are the plan and any add-on. The **Total** shows the price for each billing cycle and what is due today, and the line under it says how the first invoice is paid. 6. On a free plan, complete the verification check above the button. We check every order whose plan costs nothing. 7. Select the button under the summary. It reads **Place order and pay** when you pay after placing the order, **Place order** when your credit pays for it or your account is billed in arrears, and **Order for free** on a free plan. We do not ask for a region. We set the service up in our mail region, and the panel shows the region's code in its header. If we refuse the order, **Could not place the order** appears above the button with the reason, and your choices stay as they were. ## Pay for the order Once you place the order, its own page opens, and its title says what comes next: - **Order placed, payment due**: pay under **Pay invoice** with account credit, a saved card or PayPal account, or a new card. Setup starts once the payment goes through. [Pay and follow the order](/docs/get-started/order-a-service/#pay-and-follow-the-order) walks through each way to pay. - **Order placed**: nothing is due now, because credit paid the invoice, the plan is free or your account is billed in arrears. We are setting up the service, and the page updates on its own. - **Your order is ready**: every service in the order is active. **What you ordered** lists the service and any SMTP Relay you added, each with its status and price. To pay later, leave the page and pay the invoice under [Invoices](/docs/billing/invoices/). The service waits until it is paid. ## Order a dedicated sending IP A relay whose plan sends 100,000 emails a month or more can take a dedicated sending address. Each one is a service of its own, with its own invoice ([Dedicated sending addresses](/docs/mail/smtp-relay/sending-limits/#dedicated-sending-addresses)). 1. Open the relay's panel from [**Email**](https://www.coritan.com/dashboard/mail) and stay on the **Overview** tab. 2. On the **Sending IPs** card, select **Order dedicated IP**. On a smaller plan the button is greyed out, and the card says which plan you need. 3. The **Dedicated sending IP** page opens with the relay chosen under **Relay**. If you opened the page another way, choose the relay there. 4. If **This relay cannot take a dedicated IP yet** appears, select **Change plan** and move the relay to a larger plan first. 5. Under **Plan**, choose the address's plan. 6. Check the summary, then select **Order dedicated IP**, and pay on the order's page as for any order. When the address is ready, it is on the relay's **Sending IPs** card with an **Add-on** badge, and a **Warming up** badge while it warms up. Look for it there, on the relay's **Overview** tab. ## Result Every service in the order appears in [**Email**](https://www.coritan.com/dashboard/mail) with the status `pending`, then `provisioning` while we set it up, then `active`. We email you when each one is ready. Until then, its panel shows one of these: - **Waiting for payment**, with **Open invoices**, until the invoice is paid. - **Setting up** while we create the service. - **Not set up yet** on every tab but **Billing**, until the service exists. When the service is active, the button at the top of the order's page opens its panel. If you entered a first domain, it is on the **Domains** tab as the primary domain, with the DNS records to publish. Continue with [Add a domain to Mail Hosting](/docs/mail/mail-hosting/add-a-domain/) or [Add a sending domain](/docs/mail/smtp-relay/add-a-sending-domain/). ## Troubleshooting The button under the summary is greyed out : The line under the button names the first answer still missing, such as `Enter a valid domain, or leave it empty.` or `Complete the verification check.` Select it to go to that section. `Enter a domain, such as example.com.` : Type the domain on its own, such as `example.com`, with no `https://`, path or address. Or clear the field and add the domain later. On the **SMTP Relay** add-on, the line under the button reads `Enter a domain for SMTP Relay, such as example.com, or leave it empty.` `The verification check did not pass. Complete it and try again.` : The check on a free order expired or failed. Complete it again, then select **Order for free**. `You already have 1 free SMTP Relay. Upgrade or remove one to create another.` : Each account may hold one free relay, and a free relay counts until it has ended. Order a paid plan instead, or cancel the free relay with **Immediately** and order again once it has ended ([The free SMTP Relay plan](/docs/mail/billing/#the-free-smtp-relay-plan)). `This plan has no price yet.` : The plan cannot be ordered yet. Choose another plan, or ask [support](/docs/support/conversations/). A plan shows **Sold out** : The plan cannot be ordered right now. Choose another plan. `No Mail Hosting plans yet` or `No SMTP Relay plans yet` : The product is not in the catalogue for your account. Ask [support](/docs/support/conversations/) if you expected it there. **Could not load the plans** : The plans did not load. Select **Try again**, or reload the page. The first domain is not on the Domains tab : The order goes ahead even when the domain cannot be added, for example because it is already on another mail service. Add it from the **Domains** tab, which shows the reason if it is refused again. **No relays yet** on the dedicated IP page : A dedicated IP sends for one of your relays, and the account has no active or suspended relay. Select **Order SMTP Relay** to order one first. `Upgrade the relay's plan first.` : The relay's plan sends fewer than 100,000 emails a month. Select **Change plan** under **This relay cannot take a dedicated IP yet**, move to a larger plan, then order the address. `Could not check the relay. Reload the page and try again.` : We could not read the relay's plan. Reload the page. If **Could not load your relays** appears instead, reload the page too. ## Related - [Mail DNS records](/docs/mail/dns-records/) - [How Mail Hosting and SMTP Relay are billed](/docs/mail/billing/) - [How SMTP Relay sending limits work](/docs/mail/smtp-relay/sending-limits/) - [Order a service](/docs/get-started/order-a-service/) - [Claim a free coritan.gg name](/docs/proxies/coritan-gg-names/) ## With the API Order either product with [`POST /services/order`](/docs/api/reference/client/services/#op-post-api-v1-services-order). List the plans with [`GET /products/`](/docs/api/reference/client/catalog/#op-get-api-v1-products): Mail Hosting plans have the `module_name` `mail`, and SMTP Relay plans `smtp_relay`. Copy the plan's `config_schema` into `config` as it is, and add `domain` for a first domain. We refuse a Mail Hosting order without the plan's `mailboxes` and `storage_gb_per_mailbox`, and a relay order without its `monthly_messages`. This example orders Mail Hosting with an SMTP Relay on the same invoice: ```bash curl -X POST https://api.coritan.com/api/v1/services/order \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "product_id": 61, "pricing_id": 140, "hostname": "example.com", "config": {"mailboxes": 5, "storage_gb_per_mailbox": 10, "domains": 1, "domain": "example.com"}, "addons": [ { "product_id": 72, "config": {"monthly_messages": 25000, "domains": 0, "credentials": 0, "dedicated_ips": 0, "quota_buffer_pct": 12, "domain": "send.example.com"} } ] }' ``` `hostname` is the name the service is listed under, and the dashboard sends the first domain there. An add-on needs a price above $0, so the free relay plan is ordered on its own, with a `turnstile_token` from the verification check on the website. The answer is `201` with the new `service`, its `addons`, and `invoice_id` and `amount_due` when `requires_payment` is `true`. [Order a service](/docs/get-started/order-a-service/#with-the-api) describes the answer and the errors every order shares. These are the mail ones: - `422` with `{"detail": {"errors": [...]}}` for a config we cannot use, such as `mailboxes must be at least 1` or `monthly_messages must be at least 1000`. An add-on's message starts with its plan name. - `409` with `"error": "free_limit_reached"` when the account already holds a free relay. A dedicated sending IP is an order of the `smtp_relay` product whose `config_schema` has `"addon": "dedicated_ip"`. Send that `config_schema` with `relay_service_id`, the service id of the relay it sends for. It cannot be an add-on of another order. Check the relay first with [`GET /client/smtp-relay/{service_id}`](/docs/api/reference/client/mail/smtp-relay/#op-get-api-v1-client-smtp-relay-service-id): `limits.dedicated_ip_addon_eligible` must be `true`. We check the plan again when we set the address up, and the setup fails on a relay that sends fewer than `limits.dedicated_ip_min_monthly` emails a month. ```bash curl -X POST https://api.coritan.com/api/v1/services/order \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"product_id": 79, "pricing_id": 160, "config": {"addon": "dedicated_ip", "dedicated_ips": 1, "relay_service_id": 1187}}' ``` ## API - `POST /api/v1/services/order`: Order a platform service, and any add-ons bought with it (https://www.coritan.com/docs/api/reference/client/services/#op-post-api-v1-services-order) - `GET /api/v1/client/smtp-relay/{service_id}`: Get summary (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay/#op-get-api-v1-client-smtp-relay-service-id) # How Mail Hosting works > What a Mail Hosting service includes, how domains, mailboxes and aliases fit together, and what its Overview tab shows. Source: https://www.coritan.com/docs/mail/mail-hosting/ In the dashboard: - /dashboard/mail/…/overview: https://www.coritan.com/dashboard/mail A Mail Hosting service gives people mailboxes on your own domain. They read and send mail in a mail app over IMAP and SMTP, over JMAP, or in webmail. You manage everything from the service's panel in the dashboard: go to **Email** and open the service. ## How the parts fit together *Domains* : The names your addresses end in, such as `example.com`. You add a domain, prove you own it and publish its DNS records. See [Add a domain to Mail Hosting](/docs/mail/mail-hosting/add-a-domain/). *Mailboxes* : Addresses that store mail and can sign in, such as `alex@example.com`. Each has its own password and storage quota. See [Create and manage mailboxes](/docs/mail/mail-hosting/mailboxes/). *Aliases* : Extra addresses that deliver to one or more mailboxes on the service, including a catch-all for a whole domain. See [Forward addresses with aliases](/docs/mail/mail-hosting/aliases/). A mailbox or alias can only use a domain on the same service, and the dashboard offers only domains that are verified. Each domain can be on one mail service at a time. ## What the plan sets The plan sets how many mailboxes and domains the service may hold, and how much storage it has. The storage is one pool for the whole service: the number of mailboxes multiplied by the storage per mailbox that the plan lists. Each mailbox takes a share of that pool as its quota. A new mailbox gets an equal share by default, and you can give one mailbox more and another less, as long as the quotas together fit in the pool. The smallest quota is 100 MB. Aliases do not count against the plan. To get more mailboxes, domains or storage, see [Move to a bigger or smaller plan](/docs/mail/billing/#move-to-a-bigger-or-smaller-plan). ## What the Overview tab shows In the dashboard, go to **Email**, open the service, then the **Overview** tab. The four figures at the top show **Storage**, **Mailboxes** and **Domains** used against the plan, and the number of **Aliases**. **Mail server addresses** : The names to enter in a mail app: **SMTP** (ports 587 with STARTTLS and 465 with TLS), **IMAP** (port 993 with TLS) and **JMAP** (port 443). It also shows the **Webmail** address and the **MX suffix**, which ends the `mx1`, `mx2` names your domains' MX records point to. **Mail client setup** : The settings a mail app asks for, in the order it asks. See [Connect a mail app](/docs/mail/mail-hosting/connect-a-mail-app/). **Recent activity** : The latest delivery events from the last seven days, such as delivered, bounced or deferred messages. **Service** : The service's **Status**, **Plan**, **Region**, the date it **Renews**, when it was **Created** and its **Service ID**. When we pause the service's sending, the status reads **Sending paused**. **Tags** : Labels you add to find the service. See [Tags](/docs/get-started/tags/). **Domains** : Each domain and its verification status. Select one to open its DNS records. The panel header has **Open webmail**, which opens the webmail address in a new tab, and **Order another**, which opens the Mail Hosting order page for a second service. ## Sending from a mailbox A mailbox sends through the service's SMTP server after it signs in. The rules that apply to every mail service apply to it too: - The From address must be on a domain of the service that has passed its ownership check. - A recipient on the service's suppression list is refused with `Recipient … is suppressed after a bounce or complaint`. Bounces and spam complaints add addresses to it. - The service has an hourly sending limit, `rate_per_hour` in the [service summary](#with-the-api). Mail over it waits in the queue and goes out in a later hour. - We watch each service's bounce and complaint rates. When they cross the pause threshold, we pause the service and the panel shows **Sending is paused**. Until our staff lift the pause, its mailboxes cannot sign in, send or receive mail. See [Sending reputation and deliverability](/docs/mail/smtp-relay/deliverability/). To send mail from an application rather than a person, use [SMTP Relay](/docs/mail/smtp-relay/). ## When the service ends When you [cancel the service](/docs/billing/cancel-a-service/) and it ends, we delete its mailboxes, aliases and stored mail. Move anything you want to keep first. ## With the API Read the service summary: ```bash curl https://api.coritan.com/api/v1/client/mail/4812 \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` The fields you need most: `kind` : `hosting` for Mail Hosting. `status` : `active` once the service is ready. `quota_mailboxes`, `quota_domains`, `quota_storage_bytes` : What the plan allows. `quota_storage_bytes` is the whole pool. `counts` : How many of each thing the service holds: `mailbox`, `alias`, `group` and `domains`. `used_bytes` : The storage the mailboxes use. `rate_per_hour` : The service's hourly sending limit. `hostnames` : `smtp_host`, `imap_host` and `jmap_host` for mail apps, `webmail_url`, and `mx_hostname_base`, the MX suffix. `cluster.region`, `cluster.region_name` : The region the service runs in: its code, and its name, such as `Frankfurt`, or `null` when we have no name for it. `sending_frozen`, `review_flag`, `reputation_note` : Whether sending is paused or under review, and why. `service` : The service's `id`, `status` and `hostname`. A service that is still being set up answers `409` with `This mail service is still provisioning`. ## API - `GET /api/v1/client/mail/{service_id}`: Get summary (https://www.coritan.com/docs/api/reference/client/mail/mail/#op-get-api-v1-client-mail-service-id) # Add a domain to Mail Hosting > Add your domain to a Mail Hosting service, publish its DNS records and verify it so its mailboxes can send and receive mail. Source: https://www.coritan.com/docs/mail/mail-hosting/add-a-domain/ In the dashboard: - /dashboard/mail/…/domains: https://www.coritan.com/dashboard/mail Add the domain your addresses will be on, such as `example.com`, publish its DNS records and verify it. Once it is verified, it receives mail here and you can create mailboxes and aliases on it. ## Before you begin - A Mail Hosting service with the status `active`. [Order one](/docs/mail/order-a-mail-service/) if you have none. While a service is being set up, **Add domain…** is greyed out. - A domain whose DNS you can change. When its zone is on [Coritan DNS](/docs/websites/add-an-existing-domain/) in the same account, we publish the records for you. - Room on the plan. The **Domains** card shows how many domains the plan allows, such as `1 of 1 on this plan.` - A domain that is on no other mail service. A domain can be on one mail service at a time. > [!IMPORTANT] > Verifying a Mail Hosting domain needs its MX records to point here, and new mail follows the MX records straight away. If the domain receives mail somewhere else today, read [Move a domain that already receives mail](#move-a-domain-that-already-receives-mail) first. ## Add the domain 1. In the dashboard, go to [**Email**](https://www.coritan.com/dashboard/mail), open the Mail Hosting service, then the **Domains** tab. 2. Select **Add domain…**. 3. In **Domain**, enter the domain, such as `example.com`. Names you claimed under [coritan.gg](/docs/proxies/coritan-gg-names/) are listed beside **Your names:**; select one to fill it in. 4. Leave **Make this the primary domain** ticked if this is the service's main domain. The primary domain names the service in lists. The box starts ticked when the service has no primary domain yet. 5. Select **Add domain**. The domain's page opens, with its records on the **DNS records** card. ## Publish the records What you do depends on where the domain's DNS is: - On Coritan DNS, in this account: nothing. We have already written the records into the zone, and the card reads *We publish these into your hosted zone and keep them up to date.* A record your zone already had wins over ours; see [Records we publish for you](/docs/mail/dns-records/#records-we-publish-for-you). - On Coritan DNS, but added to this account after the domain: use the [**Publish to your zone**](/docs/mail/dns-records/#publish-to-your-zone) card on the domain's page. - A claimed coritan.gg name: nothing. We publish its records and check the domain as soon as you add it. - Anywhere else: create the records at your DNS provider, as the steps below describe. To publish the records at another DNS provider: 1. Create the ownership record first: the TXT record at `_mailverify.example.com`. Copy its name and value from the **DNS records** card. 2. Create the MX, SPF and DMARC records and the rest of the list. 3. On the domain's page, select **Verify now**. Once we find the ownership record, we make the domain's two DKIM keys. 4. Reload the page. The card now lists the two DKIM records. 5. Create the two DKIM records. [Mail DNS records](/docs/mail/dns-records/) explains every record and what its check looks for. ## Verify the domain 1. On the domain's page, select **Verify now**. 2. Read the toast. `example.com is verified.` means you are done. `example.com is still pending:` is followed by the first problem the check found. 3. If it is still pending, fix what the **Checks** row and the **The last check found problems** list show, and select **Verify now** again. We also check pending domains about every ten minutes, so a domain whose records are right verifies on its own. The card's **Status** column shows each record's result from the last check. ## Result - The domain shows `Verified` on the **Domains** tab, and **Ownership**, **MX**, **SPF** and **DKIM** are ticked. **DMARC** is shown too, but a domain is verified without it. - The domain is offered in the **Mailboxes** and **Aliases** forms. See [Create and manage mailboxes](/docs/mail/mail-hosting/mailboxes/). - Mail to addresses on the domain arrives here. - When we publish into your zone, the page shows *Ready: the records are in your zone*, with **Create your first mailbox** (or **Create mailbox** once the service has mailboxes). ## Move a domain that already receives mail The dashboard creates mailboxes only on a verified domain, and a domain verifies only once its MX records point here. To have the mailboxes ready before mail arrives, create them with the API instead: it accepts a domain as soon as its ownership record is found. 1. Add the domain as above. 2. Publish the ownership record, and add `include:_spf.` from the card to your current SPF record, so the domain keeps one SPF record. Leave your MX records as they are. 3. Select **Verify now**. The ownership check passes; the domain stays pending because of MX. 4. Reload the page, then publish the two DKIM records it now lists. 5. Create the mailboxes with the API. See [Create and manage mailboxes](/docs/mail/mail-hosting/mailboxes/#with-the-api). 6. Copy the old mail in if you want to. See [Move existing mail into a mailbox](/docs/mail/mail-hosting/move-mail-in/). 7. Replace your MX records with the ones on the **DNS records** card, and publish the rest of the list. 8. Select **Verify now**. Mail other servers already queued for the old MX hosts can still arrive there for a while after the change, so check the old mailboxes for a few days. Once you no longer send through the old provider, remove its include from your SPF record. ## Remove a domain > [!WARNING] > Removing a domain stops mail to and from it at once. If you add it again later, it gets a new ownership token and new DKIM keys, so you publish and verify it from the start. 1. Delete the mailboxes and aliases on the domain first. See [Delete a mailbox](/docs/mail/mail-hosting/mailboxes/#delete-a-mailbox) and [Forward addresses with aliases](/docs/mail/mail-hosting/aliases/). 2. On the **Domains** tab, select the domain. 3. In the **Remove this domain** card, select **Remove domain…**. 4. Type the domain name to confirm, then select **Remove domain**. The **Settings** tab has the same **Remove domain…** button for each domain, under **Danger zone**. We delete the records we published into a hosted zone. Records at another DNS provider are yours to delete. ## Troubleshooting **Add domain…** is greyed out : The service is not `active` yet, or the plan's domains are all used. A full plan shows `This plan allows 1 domain. Remove one or change the plan to add another.` Remove a domain or [move to a bigger plan](/docs/mail/billing/#move-to-a-bigger-or-smaller-plan). `Enter a domain, such as example.com.` : The **Domain** field is empty or is not a domain name. Enter the name alone, without `https://` or an address. `Enter a valid domain name such as example.com` : The API refused the name. Use letters, digits and hyphens in each label, and a real top-level domain. `Wildcard domains cannot receive mail` : Enter a name such as `example.com` rather than `*.example.com`. `That domain is already attached to a mail service` : Someone else's mail service has it. If the domain is yours, contact [support](/docs/support/conversations/). `example.com is a sending domain on your SMTP Relay service. To host mailboxes on it, remove it there and give the relay a sending subdomain such as send.example.com.` : A domain can be on one mail service. [Add a sending subdomain](/docs/mail/smtp-relay/add-a-sending-domain/) to the relay, remove the domain from the relay, then add it here. The relay can still send as `@example.com`. `example.com is already on another of your mail services` : Remove it from the other Mail Hosting service first. `example.com is a name the mail platform uses itself` or `example.com is one of the platform's own domains, so it cannot be added to this service` : The name belongs to Coritan. Use a domain of your own. `Claim a coritan.gg name before using alex.coritan.gg; it is not yours yet` : [Claim the name](/docs/proxies/coritan-gg-names/) first. `coritan.gg is the platform's own domain; claim a name under it first` means you entered `coritan.gg` itself. `This plan allows 1 domain(s)` : The plan is full. Remove a domain or [move to a bigger plan](/docs/mail/billing/#move-to-a-bigger-or-smaller-plan). `MX points to` followed by other hosts : The domain's MX records still name another provider. Replace them with the ones on the **DNS records** card. When we publish into your zone, we add no MX records while another provider's are there, so delete those yourself. `DKIM selector cor202609r not published` : The DKIM record with that selector is missing or holds a different key. Copy the whole value again; some DNS providers split long TXT values, which is fine as long as nothing is lost. `No DKIM keys yet: the mail engine has not made them` : We have not finished making the domain's keys. Select **Verify now** again in a minute. `DMARC publishes p=none, expected p=quarantine` : Your DMARC record has a different stage from the one we expect. The domain verifies without DMARC; see [DMARC](/docs/mail/dmarc/) to fix it. `The zone is busy with another change. Check the domain again in a minute.` : Another change to the same zone was being saved. Select **Verify now** again. `Remove the mailboxes and aliases on this domain first` : Delete them, then remove the domain. **Sending only** : An older domain that passed its checks before MX was required. Its MX records point elsewhere, so it sends but does not receive mail here. Publish the MX records on the card to receive mail. ## Related - [Mail DNS records](/docs/mail/dns-records/) - [Create and manage mailboxes](/docs/mail/mail-hosting/mailboxes/) - [How the DMARC stage ramp works](/docs/mail/dmarc/) - [Mail troubleshooting](/docs/mail/troubleshooting/) ## With the API Add a domain: ```bash curl -X POST https://api.coritan.com/api/v1/client/mail/4812/domains \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"domain": "example.com", "primary": true}' ``` `domain` is the name (3–253 characters) and `primary` defaults to `false`. The answer is `201` with the domain and its `records`, in the shape [the records endpoint](/docs/mail/dns-records/#with-the-api) returns. A name the service refuses answers `400` with one of the messages in [Troubleshooting](#troubleshooting). The coritan.gg claim messages answer `403`, and a service that is not active answers `409` `Service is not active`. Check the domain now: ```bash curl -X POST https://api.coritan.com/api/v1/client/mail/4812/domains/311/verify \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "domain": { "id": 311, "domain": "example.com", "status": "pending", "checks": { "ownership": true, "mx": false, "spf": true, "dkim": true, "dmarc": true } }, "result": { "ownership": true, "mx": false, "spf": true, "dkim": true, "dmarc": true, "dmarc_policy": "none", "errors": ["MX points to aspmx.example.net"], "dkim_pending_published": [] } } ``` The other domain operations: | Operation | What it does | | --- | --- | | `GET /client/mail/{service_id}/domains` | Lists the service's domains as `{"items": [...]}`, each with `status`, `checks`, `is_primary`, `dmarc_policy` and `last_error`. | | `GET /client/mail/{service_id}/domains/{domain_id}/records` | The records to publish. See [Mail DNS records](/docs/mail/dns-records/#with-the-api). | | `PATCH /client/mail/{service_id}/domains/{domain_id}/dmarc` | Sets or holds the DMARC stage. See [DMARC](/docs/mail/dmarc/#with-the-api). | | `DELETE /client/mail/{service_id}/domains/{domain_id}` | Removes the domain and answers `{"ok": true}`. | A domain that is not on the service answers `404` `Domain not found`. ## API - `GET /api/v1/client/mail/{service_id}/domains`: List domains (https://www.coritan.com/docs/api/reference/client/mail/mail-domains/#op-get-api-v1-client-mail-service-id-domains) - `POST /api/v1/client/mail/{service_id}/domains`: Add domain (https://www.coritan.com/docs/api/reference/client/mail/mail-domains/#op-post-api-v1-client-mail-service-id-domains) - `DELETE /api/v1/client/mail/{service_id}/domains/{domain_id}`: Remove domain (https://www.coritan.com/docs/api/reference/client/mail/mail-domains/#op-delete-api-v1-client-mail-service-id-domains-domain-id) - `PATCH /api/v1/client/mail/{service_id}/domains/{domain_id}/dmarc`: Set DMARC (https://www.coritan.com/docs/api/reference/client/mail/mail-domains/#op-patch-api-v1-client-mail-service-id-domains-domain-id-dmarc) - `GET /api/v1/client/mail/{service_id}/domains/{domain_id}/records`: Domain records (https://www.coritan.com/docs/api/reference/client/mail/mail-domains/#op-get-api-v1-client-mail-service-id-domains-domain-id-records) - `POST /api/v1/client/mail/{service_id}/domains/{domain_id}/verify`: Verify domain (https://www.coritan.com/docs/api/reference/client/mail/mail-domains/#op-post-api-v1-client-mail-service-id-domains-domain-id-verify) # Create and manage mailboxes > Create mailboxes on your domain, reset their passwords, change storage quotas, and turn off or delete a mailbox. Source: https://www.coritan.com/docs/mail/mail-hosting/mailboxes/ In the dashboard: - /dashboard/mail/…/mailboxes: https://www.coritan.com/dashboard/mail A *mailbox* is an address with its own storage, such as `alex@example.com`, that people sign in to with webmail or a mail app. Each Mail Hosting plan allows a number of mailboxes and a pool of storage they share. This page covers creating mailboxes and looking after them. ## Before you begin - A Mail Hosting service with a domain whose ownership you have proved. See [Add a domain to Mail Hosting](/docs/mail/mail-hosting/add-a-domain/). - Room on the plan. The **Mailboxes** card shows how many the plan allows, such as `3 of 5 on this plan.` ## Create a mailbox 1. In the dashboard, go to [**Email**](https://www.coritan.com/dashboard/mail), open the Mail Hosting service, then the **Mailboxes** tab. 2. Select **Create mailbox…**. 3. In **Name**, enter the part before the @, such as `alex`. The hint under the field shows the full address. 4. In **Domain**, choose the domain. The list holds every domain whose ownership you have proved. A domain whose other DNS records are not in place yet is marked `not fully set up`: you can create the mailbox, but its mail may land in spam or not arrive until you finish the domain, and the mailbox's row says `Domain not fully set up` until then. 5. Optionally, enter a **Display name**, such as `Alex Morgan`. 6. In **Quota (GB)**, set the mailbox's storage. The field starts at an equal share of the plan's storage, and its hint shows the plan's total. 7. Under **Password**, choose **Generate a strong password**, or **Set one now** and enter it in **New password** (at least 12 characters). 8. Select **Create mailbox**. A dialog shows the **Address**, the **Password** if we generated it, and the **IMAP**, **SMTP** and **Webmail** settings. Copy the password now: we show a generated password once and cannot show it again. The mailbox appears in the list, with its **Security**, **Quota**, **Password set** and **Enabled** columns. To open it in webmail with its address filled in, open the menu at the end of its row and select **Open webmail**. To set up a mail app, see [Connect a mail app to Mail Hosting](/docs/mail/mail-hosting/connect-a-mail-app/). ## Reset a mailbox password 1. On the **Mailboxes** tab, open the menu at the end of the mailbox's row. 2. Select **Reset password…**. 3. Choose **Generate a strong password**, or **Set one now** and enter the new password. 4. Select **Reset password**. The dialog that follows shows a generated password once. Resetting the password signs the mailbox out of webmail everywhere, and its app passwords keep working. Update the password in every mail app that uses it. > [!NOTE] > A generated password also turns off two-factor sign-in for the mailbox, because whoever holds the new password is taken to be its owner. A password you set yourself keeps two-factor sign-in on. The **Security** column shows the change after you reload the page. ## Change a mailbox's quota 1. On the **Mailboxes** tab, open the menu at the end of the mailbox's row. 2. Select **Change quota…**. 3. In **Quota (GB)**, enter the new size. The hint shows the current quota and the plan's total. 4. Select **Save quota**. The toast confirms the new quota. A quota is at least 100 MB, and all the mailboxes' quotas together cannot exceed the plan's storage. To give one mailbox more, shrink another first or [move to a bigger plan](/docs/mail/billing/#move-to-a-bigger-or-smaller-plan). ## Turn a mailbox off or on On the **Mailboxes** tab, use the switch in the mailbox's **Enabled** column. A mailbox you turn off cannot sign in, send or receive mail, and is signed out of webmail. Its stored mail stays, and turning it back on restores everything. A mailbox that is off still counts towards the plan. ## Delete a mailbox > [!CAUTION] > Deleting a mailbox destroys every message in it, and you cannot get them back. Aliases that deliver to it deliver nowhere until you change them. 1. On the **Mailboxes** tab, open the menu at the end of the mailbox's row. 2. Select **Delete mailbox…**. 3. Type the mailbox's address to confirm, then select **Delete mailbox**. The mailbox leaves the list, and its place and storage go back to the plan. ## Troubleshooting **Create mailbox…** is greyed out : The plan's mailboxes are all in use, or the service is not active. A full plan shows `This plan allows 5 mailboxes. Delete one or change the plan to add another.` Delete a mailbox, or [move to a bigger plan](/docs/mail/billing/#move-to-a-bigger-or-smaller-plan). **Prove you own a domain first** : No domain on the service has its ownership proved yet, so the form has nothing to offer. Publish the domain's ownership record, shown on the **Domains** tab, then [check the domain](/docs/mail/mail-hosting/add-a-domain/#verify-the-domain). `Use only letters, digits, dots, dashes, plus signs and underscores.` : The name has another character, or starts or ends with a dot, dash, plus sign or underscore. Names are at most 64 characters and are stored in lower case. `That address already exists` : The address is already a mailbox or an alias. Choose another name, or delete the alias first. `Not enough storage left on this plan for another mailbox of that size` : The other mailboxes' quotas leave too little room. Enter a smaller **Quota (GB)**, shrink another mailbox, or [move to a bigger plan](/docs/mail/billing/#move-to-a-bigger-or-smaller-plan). `That would exceed the plan's total storage` : The new quota does not fit beside the others. Shrink another mailbox first. `The domain is not registered in the mail engine yet; check the domain again in a minute` : The domain was verified moments ago and its setup is not finished. Select **Verify now** on the domain's page, then try again. The address shows a warning and the mailbox is off : We turn a mailbox off by ourselves when it suddenly sends far more than it usually does, which is what a stolen password looks like. The warning under the address says what we saw, and we email the account owner. [Reset the password](#reset-a-mailbox-password), then turn the mailbox back on. The warning stays under the address after that. ## Related - [Secure a mailbox](/docs/mail/mail-hosting/mailbox-security/) - [Connect a mail app to Mail Hosting](/docs/mail/mail-hosting/connect-a-mail-app/) - [Move existing mail into a mailbox](/docs/mail/mail-hosting/move-mail-in/) - [Forward addresses with aliases](/docs/mail/mail-hosting/aliases/) ## With the API Create a mailbox with a generated password: ```bash curl -X POST https://api.coritan.com/api/v1/client/mail/4812/mailboxes \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"domain_id": 311, "local_part": "alex", "display_name": "Alex Morgan", "quota_bytes": 5368709120}' ``` The body takes these fields: `domain_id` : The domain, from `GET /client/mail/{service_id}/domains`. The API accepts a domain as soon as its ownership check has passed, before it is verified. See [Move a domain that already receives mail](/docs/mail/mail-hosting/add-a-domain/#move-a-domain-that-already-receives-mail). `local_part` : The part before the @, 1–64 characters: letters, digits and `.` `_` `+` `-`, starting and ending with a letter or digit. `password` : Optional, 12–128 characters. Leave it out and we generate a 24-character password. `display_name` : Optional, up to 255 characters. `quota_bytes` : Optional, at least `104857600` (100 MB). Leave it out for an equal share of the plan's storage. The answer is `201` with the mailbox. `password` holds the generated password, shown this once, or `null` when you set one: ```json { "id": 5120, "tenant_id": 902, "domain_id": 311, "kind": "mailbox", "address": "alex@example.com", "local_part": "alex", "display_name": "Alex Morgan", "quota_bytes": 5368709120, "targets": [], "status": "active", "password_set_at": "2026-09-16T10:52:03.418207+00:00", "created_at": "2026-09-16T10:52:03.418207+00:00", "totp_enabled": false, "app_passwords": 0, "frozen_reason": null, "user_id": null, "password": "q7!Rk2mV9x#Tz4pLw8=Hs3Nd" } ``` The other operations: | Operation | Body | Answer | | --- | --- | --- | | `GET /client/mail/{service_id}/mailboxes` | | `{"items": [...]}`, each mailbox as above with `locked` (always `null` for a mailbox) in place of `password` | | `POST /client/mail/{service_id}/mailboxes/{account_id}/password` | `{"password": "..."}`, or `{}` to generate one | `{"id", "address", "password"}` with the new password | | `PATCH /client/mail/{service_id}/mailboxes/{account_id}/quota` | `{"quota_bytes": 10737418240}` | The mailbox | | `PATCH /client/mail/{service_id}/mailboxes/{account_id}/enabled` | `{"enabled": false}` | The mailbox, with `status` `disabled` or `active` | | `DELETE /client/mail/{service_id}/mailboxes/{account_id}` | | `{"ok": true}` | A refused request answers `400` with one of the messages in [Troubleshooting](#troubleshooting), or with `This plan allows 5 mailboxes`, `Domain not found on this service` or `Prove you own example.com first: publish the ownership record shown under Domains, then check the domain`. A value outside a field's range answers `422`, an id that is not a mailbox on the service answers `404` `Not found`, and a failure on our side answers `502` with a message that starts `Mail engine error:`. ### On an SMTP Relay service A relay has no mailboxes, but the same paths exist under `/client/smtp-relay/{service_id}/mailboxes`: - `GET` answers `{"items": []}`, and `POST` answers `400` `Mailboxes are only available on Mail Hosting services`. - `.../{account_id}/password` and `.../{account_id}/enabled` accept an SMTP credential's id and act on the credential. `password` is the only way to give a credential a password you choose; see [Send over SMTP with credentials](/docs/mail/smtp-relay/credentials/#with-the-api). - Every other path under `.../mailboxes/{account_id}` answers `404` `Not found`. ## API - `GET /api/v1/client/mail/{service_id}/mailboxes`: List mailboxes (https://www.coritan.com/docs/api/reference/client/mail/mail-mailboxes/#op-get-api-v1-client-mail-service-id-mailboxes) - `POST /api/v1/client/mail/{service_id}/mailboxes`: Create mailbox (https://www.coritan.com/docs/api/reference/client/mail/mail-mailboxes/#op-post-api-v1-client-mail-service-id-mailboxes) - `POST /api/v1/client/mail/{service_id}/mailboxes/{account_id}/password`: Mailbox password (https://www.coritan.com/docs/api/reference/client/mail/mail-mailboxes/#op-post-api-v1-client-mail-service-id-mailboxes-account-id-password) - `PATCH /api/v1/client/mail/{service_id}/mailboxes/{account_id}/quota`: Mailbox quota (https://www.coritan.com/docs/api/reference/client/mail/mail-mailboxes/#op-patch-api-v1-client-mail-service-id-mailboxes-account-id-quota) - `PATCH /api/v1/client/mail/{service_id}/mailboxes/{account_id}/enabled`: Mailbox enabled (https://www.coritan.com/docs/api/reference/client/mail/mail-mailboxes/#op-patch-api-v1-client-mail-service-id-mailboxes-account-id-enabled) - `DELETE /api/v1/client/mail/{service_id}/mailboxes/{account_id}`: Delete mailbox (https://www.coritan.com/docs/api/reference/client/mail/mail-mailboxes/#op-delete-api-v1-client-mail-service-id-mailboxes-account-id) - `GET /api/v1/client/smtp-relay/{service_id}/mailboxes`: List mailboxes (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-mailboxes/#op-get-api-v1-client-smtp-relay-service-id-mailboxes) - `POST /api/v1/client/smtp-relay/{service_id}/mailboxes`: Create mailbox (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-mailboxes/#op-post-api-v1-client-smtp-relay-service-id-mailboxes) - `DELETE /api/v1/client/smtp-relay/{service_id}/mailboxes/{account_id}`: Delete mailbox (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-mailboxes/#op-delete-api-v1-client-smtp-relay-service-id-mailboxes-account-id) - `GET /api/v1/client/smtp-relay/{service_id}/mailboxes/{account_id}/app-passwords`: Mailbox app passwords (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-mailboxes/#op-get-api-v1-client-smtp-relay-service-id-mailboxes-account-id-app-passwords) - `POST /api/v1/client/smtp-relay/{service_id}/mailboxes/{account_id}/app-passwords`: Mailbox app password create (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-mailboxes/#op-post-api-v1-client-smtp-relay-service-id-mailboxes-account-id-app-passwords) - `DELETE /api/v1/client/smtp-relay/{service_id}/mailboxes/{account_id}/app-passwords/{credential_id}`: Mailbox app password delete (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-mailboxes/#op-delete-api-v1-client-smtp-relay-service-id-mailboxes-account-id-app-passwords-cr) - `PATCH /api/v1/client/smtp-relay/{service_id}/mailboxes/{account_id}/enabled`: Mailbox enabled (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-mailboxes/#op-patch-api-v1-client-smtp-relay-service-id-mailboxes-account-id-enabled) - `GET /api/v1/client/smtp-relay/{service_id}/mailboxes/{account_id}/imports`: Mailbox imports (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-mailboxes/#op-get-api-v1-client-smtp-relay-service-id-mailboxes-account-id-imports) - `POST /api/v1/client/smtp-relay/{service_id}/mailboxes/{account_id}/imports`: Mailbox import start (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-mailboxes/#op-post-api-v1-client-smtp-relay-service-id-mailboxes-account-id-imports) - `POST /api/v1/client/smtp-relay/{service_id}/mailboxes/{account_id}/imports/oauth`: Mailbox import sign in (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-mailboxes/#op-post-api-v1-client-smtp-relay-service-id-mailboxes-account-id-imports-oauth) - `POST /api/v1/client/smtp-relay/{service_id}/mailboxes/{account_id}/imports/upload`: Mailbox import upload (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-mailboxes/#op-post-api-v1-client-smtp-relay-service-id-mailboxes-account-id-imports-upload) - `POST /api/v1/client/smtp-relay/{service_id}/mailboxes/{account_id}/imports/{import_id}/{action}`: Mailbox import steer (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-mailboxes/#op-post-api-v1-client-smtp-relay-service-id-mailboxes-account-id-imports-import-id) - `POST /api/v1/client/smtp-relay/{service_id}/mailboxes/{account_id}/password`: Mailbox password (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-mailboxes/#op-post-api-v1-client-smtp-relay-service-id-mailboxes-account-id-password) - `PATCH /api/v1/client/smtp-relay/{service_id}/mailboxes/{account_id}/quota`: Mailbox quota (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-mailboxes/#op-patch-api-v1-client-smtp-relay-service-id-mailboxes-account-id-quota) - `GET /api/v1/client/smtp-relay/{service_id}/mailboxes/{account_id}/sessions`: Mailbox sessions (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-mailboxes/#op-get-api-v1-client-smtp-relay-service-id-mailboxes-account-id-sessions) - `DELETE /api/v1/client/smtp-relay/{service_id}/mailboxes/{account_id}/sessions`: Mailbox sessions end (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-mailboxes/#op-delete-api-v1-client-smtp-relay-service-id-mailboxes-account-id-sessions) - `DELETE /api/v1/client/smtp-relay/{service_id}/mailboxes/{account_id}/sessions/{session_id}`: Mailbox session end (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-mailboxes/#op-delete-api-v1-client-smtp-relay-service-id-mailboxes-account-id-sessions-session) - `POST /api/v1/client/smtp-relay/{service_id}/mailboxes/{account_id}/totp`: Mailbox totp enable (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-mailboxes/#op-post-api-v1-client-smtp-relay-service-id-mailboxes-account-id-totp) - `DELETE /api/v1/client/smtp-relay/{service_id}/mailboxes/{account_id}/totp`: Mailbox totp disable (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-mailboxes/#op-delete-api-v1-client-smtp-relay-service-id-mailboxes-account-id-totp) # Secure a mailbox > Create app passwords for mail apps, turn on two-factor sign-in for a mailbox, and end its webmail sessions. Source: https://www.coritan.com/docs/mail/mail-hosting/mailbox-security/ In the dashboard: - /dashboard/mail/…/mailboxes: https://www.coritan.com/dashboard/mail Each mailbox on a Mail Hosting service has three controls that decide who can sign in to it: *app passwords* for mail apps, *two-factor sign-in* with an authenticator app, and the list of browsers signed in to webmail. You reach all three from the mailbox's menu on the **Mailboxes** tab. The mailbox's own user can manage the same things in webmail; see [Protect your webmail mailbox](/docs/mail/webmail/security/). ## Before you begin - A mailbox on a Mail Hosting service. See [Create and manage mailboxes](/docs/mail/mail-hosting/mailboxes/). - For two-factor sign-in, an authenticator app on a phone or computer. Any app that reads an `otpauth://` link or takes a key by hand works. ## Create an app password An app password signs one mail app in to the mailbox in place of its password, and skips the two-factor code. Give each app its own, so you can revoke one without touching the others. 1. In the dashboard, go to [**Email**](https://www.coritan.com/dashboard/mail), open the Mail Hosting service, then the **Mailboxes** tab. 2. Open the menu at the end of the mailbox's row and select **App passwords…**. 3. Optionally, enter a **Label** that names the app or device, such as `phone`. It can be up to 64 characters. 4. Select **Create app password**. 5. Copy the **App password** from the dialog that opens. We show it once and cannot show it again. In the mail app, use the mailbox's address as the username and the app password as the password. The mailbox's **Security** column counts its app passwords, such as `2 app passwords`. A mailbox holds at most ten app passwords. Each browser signed in to webmail uses one of them, so the more browsers are signed in, the fewer you can create. ## Revoke an app password 1. On the **Mailboxes** tab, open the menu at the end of the mailbox's row and select **App passwords…**. 2. Find the password by its label. Each row also shows the date it was created. 3. Select **Revoke…**, then **Revoke app password**. The app that used it can no longer sign in. The list also shows the passwords the mailbox's user made in webmail for their devices, and revoking one here signs that device out too. ## Turn on two-factor sign-in With two-factor sign-in on, webmail asks for a six-digit code from an authenticator app after the password. 1. On the **Mailboxes** tab, open the menu at the end of the mailbox's row. 2. Select **Turn on two-factor sign-in**. 3. From the dialog that opens, add the mailbox to the authenticator app: open the **otpauth URI** on the device with the app, or type the **Secret** into it. We show them once. The app lists the mailbox under the service's mail server name, such as `mail-fra.coritan.com`. The **Security** column changes to **Two-factor on**, and every browser signed in to webmail is signed out. The next time someone signs in, webmail shows **Enter your code** after the password and takes the code in **Code**. Codes change every 30 seconds. Mail apps cannot ask for a code. A mail app that signed in with the mailbox's password stops working until you give it an [app password](#create-an-app-password). App passwords made before you turned two-factor sign-in on keep working. ## Turn off two-factor sign-in To turn off two-factor sign-in from the dashboard, give the mailbox a new generated password: 1. On the **Mailboxes** tab, open the menu at the end of the mailbox's row. 2. Select **Reset password…**. 3. Choose **Generate a strong password**, then select **Reset password**. 4. Copy the new password from the dialog and give it to the mailbox's user. The mailbox goes back to signing in with the password alone. Reload the page to see **Two-factor off** in the **Security** column. Resetting the password also signs the mailbox out of webmail everywhere; its app passwords keep working. The mailbox's user can turn it off without a new password. In [webmail](https://www.coritan.com/webmail), signed in with the mailbox's address, they turn off **Ask for a code when signing in** on the **Security** tab of settings and confirm with the mailbox's password. [Turn off two-factor authentication](/docs/mail/webmail/security/#turn-off-two-factor-authentication) explains what changes when they open the mailbox through a Coritan account instead. > [!NOTE] > The mailbox menu also shows **Turn off two-factor sign-in…** while two-factor sign-in is on. It needs the mailbox's password, which the dashboard does not ask for, so it always ends with `Could not turn off two-factor sign-in`. Reset the password as above instead. ## End webmail sessions A *webmail session* is one browser signed in to webmail as the mailbox. End one when a device is lost, or when someone should no longer read the mailbox. 1. On the **Mailboxes** tab, open the menu at the end of the mailbox's row and select **Webmail sessions…**. 2. Read the list. Each row names the browser and system, such as `Firefox on Windows`, then the IP address, when it was last used and when it started. Hover over the name to see the browser's full description. 3. To sign out one browser, select **End session…** on its row, then **End session**. 4. To sign out every browser, select **Sign out everywhere…**, then **Sign out everywhere**. The browser has to sign in again with the mailbox's address and password. Ending sessions does not change the password, so anyone who knows it can sign in again: to lock someone out, [reset the password](/docs/mail/mail-hosting/mailboxes/#reset-a-mailbox-password) as well. Mail apps keep working, because they use the password or an app password rather than a session. A session also ends by itself: - after 30 days, or after 7 days without use; - when a sixth browser signs in to the mailbox, which ends the one used longest ago; - when you reset the password, turn two-factor sign-in on or off, or turn the mailbox off or delete it. ## Troubleshooting `Turning two-factor login off needs the mailbox password; reset the password to clear it instead` : **Turn off two-factor sign-in…** cannot work from the dashboard. [Reset the password](#turn-off-two-factor-sign-in) with a generated one, or ask the mailbox's user to turn it off in webmail. A mail app stopped signing in after two-factor sign-in went on : The app signs in with the mailbox's password, which now needs a code the app cannot send. [Create an app password](#create-an-app-password) and enter it in the app in place of the password. `Mail engine error: AppPassword not created` : The mail server refused to make the password. The mailbox may already hold ten app passwords, counting one for each browser signed in to webmail. Revoke one you no longer use, or end a webmail session, then try again. `Mailbox is not registered in the engine yet` : The mailbox was created moments ago and its setup has not finished. Wait a minute, then try again. A browser keeps being signed out of webmail : A mailbox keeps at most five webmail sessions, so signing in on a sixth browser ends the one used longest ago. A password reset, a two-factor change and turning the mailbox off end every session. **No one is signed in to the webmail** : No browser holds a session for the mailbox. Mail apps never appear in this list. ## Related - [Create and manage mailboxes](/docs/mail/mail-hosting/mailboxes/) - [Connect a mail app to Mail Hosting](/docs/mail/mail-hosting/connect-a-mail-app/) - [Protect your webmail mailbox](/docs/mail/webmail/security/) ## With the API All the paths below sit under `/client/mail/{service_id}/mailboxes/{account_id}`, where `account_id` is the mailbox's `id` from `GET /client/mail/{service_id}/mailboxes`. Create an app password: ```bash curl -X POST https://api.coritan.com/api/v1/client/mail/4812/mailboxes/5120/app-passwords \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"label": "phone"}' ``` `label` is 1–64 characters and defaults to `app password`. The answer is `201`, and `secret` is the password, returned this once: ```json { "id": 5120, "address": "alex@example.com", "credential_id": "b7", "label": "phone", "secret": "k3Vd9QxT2mLw7RpZ" } ``` Turn on two-factor sign-in: ```bash curl -X POST https://api.coritan.com/api/v1/client/mail/4812/mailboxes/5120/totp \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` The answer carries the `secret` and the `otpauth_uri` once. The URI names SHA-1, six digits and a 30-second period, and its issuer is the service's mail server name: ```json { "id": 5120, "address": "alex@example.com", "otpauth_uri": "otpauth://totp/mail-fra.coritan.com%3Aalex%40example.com?secret=JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP&issuer=mail-fra.coritan.com&algorithm=SHA1&digits=6&period=30", "secret": "JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP" } ``` The other operations: | Operation | Answer | | --- | --- | | `GET .../app-passwords` | `{"items": [...]}`, each with `credential_id`, `label`, `created_at` and `expires_at`. Webmail sessions are not listed. | | `DELETE .../app-passwords/{credential_id}` | `{"deleted": true}`, or `404` `App password not found` | | `DELETE .../totp` | Always `400` `Turning two-factor login off needs the mailbox password; reset the password to clear it instead`. Send `{}` to `POST .../password` instead. | | `GET .../sessions` | `{"items": [...]}`, most recently used first | | `DELETE .../sessions/{session_id}` | `204`, or `404` `Session not found` | | `DELETE .../sessions` | `204`, after ending every session | Each session in `GET .../sessions` has these fields: `id` : The session's id, for `DELETE .../sessions/{session_id}`. `created_at`, `last_used_at` : When it signed in and when it was last used, in UTC. `expires_at` : When it ends if nobody uses it from now on: 7 days after `last_used_at`, and never more than 30 days after `created_at`. `ip`, `user_agent` : The IP address and the browser's own description of itself. An `account_id` that is not a mailbox on the service answers `404` `Not found`, and a failure on our side answers `502` with a message that starts `Mail engine error:`. ## API - `GET /api/v1/client/mail/{service_id}/mailboxes/{account_id}/app-passwords`: Mailbox app passwords (https://www.coritan.com/docs/api/reference/client/mail/mail-mailboxes/#op-get-api-v1-client-mail-service-id-mailboxes-account-id-app-passwords) - `POST /api/v1/client/mail/{service_id}/mailboxes/{account_id}/app-passwords`: Mailbox app password create (https://www.coritan.com/docs/api/reference/client/mail/mail-mailboxes/#op-post-api-v1-client-mail-service-id-mailboxes-account-id-app-passwords) - `DELETE /api/v1/client/mail/{service_id}/mailboxes/{account_id}/app-passwords/{credential_id}`: Mailbox app password delete (https://www.coritan.com/docs/api/reference/client/mail/mail-mailboxes/#op-delete-api-v1-client-mail-service-id-mailboxes-account-id-app-passwords-credenti) - `POST /api/v1/client/mail/{service_id}/mailboxes/{account_id}/totp`: Mailbox totp enable (https://www.coritan.com/docs/api/reference/client/mail/mail-mailboxes/#op-post-api-v1-client-mail-service-id-mailboxes-account-id-totp) - `DELETE /api/v1/client/mail/{service_id}/mailboxes/{account_id}/totp`: Mailbox totp disable (https://www.coritan.com/docs/api/reference/client/mail/mail-mailboxes/#op-delete-api-v1-client-mail-service-id-mailboxes-account-id-totp) - `GET /api/v1/client/mail/{service_id}/mailboxes/{account_id}/sessions`: Mailbox sessions (https://www.coritan.com/docs/api/reference/client/mail/mail-mailboxes/#op-get-api-v1-client-mail-service-id-mailboxes-account-id-sessions) - `DELETE /api/v1/client/mail/{service_id}/mailboxes/{account_id}/sessions`: Mailbox sessions end (https://www.coritan.com/docs/api/reference/client/mail/mail-mailboxes/#op-delete-api-v1-client-mail-service-id-mailboxes-account-id-sessions) - `DELETE /api/v1/client/mail/{service_id}/mailboxes/{account_id}/sessions/{session_id}`: Mailbox session end (https://www.coritan.com/docs/api/reference/client/mail/mail-mailboxes/#op-delete-api-v1-client-mail-service-id-mailboxes-account-id-sessions-session-id) # Forward addresses with aliases > Create an alias that delivers to one or more mailboxes, or a catch-all that takes every unknown address on a domain. Source: https://www.coritan.com/docs/mail/mail-hosting/aliases/ In the dashboard: - /dashboard/mail/…/aliases: https://www.coritan.com/dashboard/mail An *alias* is an address with no storage of its own that delivers its mail to mailboxes on the same service. Use one for a role address such as `sales@example.com`, or for a *catch-all* that takes the mail sent to every address on a domain that has no mailbox or alias. Aliases do not count against the plan's mailboxes. An alias takes one of three forms, decided by what you enter: | You enter | The alias | | --- | --- | | A name and one mailbox | Delivers to that mailbox. | | A name and several mailboxes | Delivers a copy to each of them, up to 50. | | `*` and one mailbox | Is the domain's catch-all. A domain can have one. | An alias delivers only to mailboxes on the same Mail Hosting service. To send a mailbox's mail on to an address somewhere else, its user sets up forwarding in webmail; see [Forward your mail to another address](/docs/mail/webmail/forwarding/). ## Before you begin - A verified domain on the service. See [Add a domain to Mail Hosting](/docs/mail/mail-hosting/add-a-domain/). - The mailboxes the alias delivers to. See [Create and manage mailboxes](/docs/mail/mail-hosting/mailboxes/). ## Create an alias 1. In the dashboard, go to [**Email**](https://www.coritan.com/dashboard/mail), open the Mail Hosting service, then the **Aliases** tab. 2. Select **Create alias…**. 3. In **Alias**, enter the part before the @, such as `sales`. The hint under the field shows the full address. 4. In **Domain**, choose the domain. Only verified domains are listed. 5. In **Delivers to**, enter a mailbox's address and press Enter, or select it under **Mailboxes here**. Repeat for each mailbox. 6. Select **Create alias**. The alias appears in the list with its **Delivers to** addresses and the date it was **Created**. Mail sent to it arrives in each of those mailboxes. ## Create a catch-all A catch-all takes mail for any address on the domain that is not a mailbox or an alias, such as a mistyped name. It also takes spam sent to made-up addresses, so check its mailbox now and then. 1. On the **Aliases** tab, select **Create alias…**. 2. In **Alias**, enter `*`. 3. In **Domain**, choose the domain. 4. In **Delivers to**, add exactly one mailbox. 5. Select **Create alias**. The alias shows as `*@example.com` with a **Catch-all** badge. ## Change where an alias delivers An alias cannot be edited. To change its mailboxes, [delete the alias](#delete-an-alias), then [create it again](#create-an-alias) with the new list. Mail sent to the address in between does not arrive, so do both steps together. ## Delete an alias > [!WARNING] > Once you delete an alias, mail sent to its address bounces back to the sender, or goes to the domain's catch-all if it has one. The mailboxes it delivered to, and the mail already in them, stay as they are. 1. On the **Aliases** tab, select **Delete…** on the alias's row. 2. Type the alias's address to confirm, then select **Delete alias**. The alias leaves the list. Deleting a catch-all makes mail to unknown addresses on the domain bounce again. ## Troubleshooting `Destinations must be mailboxes on this service` : One of the **Delivers to** addresses is not a mailbox on this service. The hint under the field suggests addresses elsewhere work, but they do not. Remove the address, or [create the mailbox](/docs/mail/mail-hosting/mailboxes/#create-a-mailbox) first. To reach an address elsewhere, use [forwarding in webmail](/docs/mail/webmail/forwarding/). `A catch-all forwards to exactly one mailbox` : A `*` alias has more than one address in **Delivers to**. Keep one. `That address already exists` : The address is already a mailbox or an alias, or the domain already has a catch-all. Choose another name, or delete the existing alias first. `Use only letters, digits, dots, dashes, plus signs and underscores, or * for a catch-all.` : The name has another character, or starts or ends with a dot, dash, plus sign or underscore. Names are at most 64 characters and are stored in lower case. **Verify a domain first** : The service has no verified domain yet. [Verify a domain](/docs/mail/mail-hosting/add-a-domain/#verify-the-domain), then create the alias. `The domain is not registered in the mail engine yet; check the domain again in a minute` : The domain was verified moments ago and its setup is not finished. Select **Verify now** on the domain's page, then try again. An alias delivers nothing : A mailbox it delivered to was deleted. An alias to one mailbox then delivers nowhere, and an alias to several stops delivering to the deleted one. [Delete the alias](#delete-an-alias) and create it again with mailboxes that exist. ## Related - [Create and manage mailboxes](/docs/mail/mail-hosting/mailboxes/) - [Forward your mail to another address](/docs/mail/webmail/forwarding/) - [Add a domain to Mail Hosting](/docs/mail/mail-hosting/add-a-domain/) ## With the API Create an alias that delivers to two mailboxes: ```bash curl -X POST https://api.coritan.com/api/v1/client/mail/4812/aliases \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"domain_id": 311, "local_part": "sales", "targets": ["alex@example.com", "sam@example.com"]}' ``` The body takes these fields: `domain_id` : The domain, from `GET /client/mail/{service_id}/domains`. The API accepts a domain as soon as its ownership check has passed, before it is verified. `local_part` : The part before the @, 1–64 characters: letters, digits and `.` `_` `+` `-`, starting and ending with a letter or digit. `*` makes the domain's catch-all. `targets` : 1–50 addresses of mailboxes on this service. We lower-case them and drop duplicates. A catch-all takes exactly one. The answer is `201` with the alias: ```json { "id": 5133, "tenant_id": 902, "domain_id": 311, "kind": "alias", "address": "sales@example.com", "local_part": "sales", "display_name": null, "quota_bytes": null, "targets": ["alex@example.com", "sam@example.com"], "status": "active", "password_set_at": null, "created_at": "2026-09-16T11:04:27.160358+00:00", "totp_enabled": false, "app_passwords": 0, "frozen_reason": null, "user_id": null } ``` The other operations: | Operation | Answer | | --- | --- | | `GET /client/mail/{service_id}/aliases` | `{"items": [...]}`, each alias as above with `locked` set to `null` | | `DELETE /client/mail/{service_id}/aliases/{account_id}` | `{"ok": true}` | A refused request answers `400` with one of the messages in [Troubleshooting](#troubleshooting), with `Use letters, digits and . _ + - for the part before @`, with `An alias needs at least one destination address` when no target contains an @, or with `Domain not found on this service`. A body outside the field ranges answers `422`, an `account_id` that is not an alias on the service answers `404` `Not found`, and a failure on our side answers `502` with a message that starts `Mail engine error:`. On an SMTP Relay service the same paths exist under `/client/smtp-relay/{service_id}/aliases`: `GET` answers `{"items": []}`, `POST` answers `400` `Aliases are only available on Mail Hosting services`, and `DELETE` answers `404` `Not found`. ## API - `GET /api/v1/client/mail/{service_id}/aliases`: List aliases (https://www.coritan.com/docs/api/reference/client/mail/mail-aliases/#op-get-api-v1-client-mail-service-id-aliases) - `POST /api/v1/client/mail/{service_id}/aliases`: Create alias (https://www.coritan.com/docs/api/reference/client/mail/mail-aliases/#op-post-api-v1-client-mail-service-id-aliases) - `DELETE /api/v1/client/mail/{service_id}/aliases/{account_id}`: Delete alias (https://www.coritan.com/docs/api/reference/client/mail/mail-aliases/#op-delete-api-v1-client-mail-service-id-aliases-account-id) - `GET /api/v1/client/smtp-relay/{service_id}/aliases`: List aliases (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-aliases/#op-get-api-v1-client-smtp-relay-service-id-aliases) - `POST /api/v1/client/smtp-relay/{service_id}/aliases`: Create alias (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-aliases/#op-post-api-v1-client-smtp-relay-service-id-aliases) - `DELETE /api/v1/client/smtp-relay/{service_id}/aliases/{account_id}`: Delete alias (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-aliases/#op-delete-api-v1-client-smtp-relay-service-id-aliases-account-id) # Connect a mail app to Mail Hosting > Set up a desktop or phone mail app with your mailbox, using automatic setup or the IMAP, SMTP and JMAP server addresses. Source: https://www.coritan.com/docs/mail/mail-hosting/connect-a-mail-app/ In the dashboard: - /dashboard/mail/…/overview: https://www.coritan.com/dashboard/mail Any mail app that speaks IMAP and SMTP can use a Mail Hosting mailbox: it reads mail over IMAP and sends over SMTP, with the mailbox's address and password. Mail Hosting does not offer POP3. This page covers finding the server names for your service and entering them in an app. ## Before you begin - A mailbox and its password. See [Create and manage mailboxes](/docs/mail/mail-hosting/mailboxes/). - If two-factor sign-in is on for the mailbox, an app password for the app. See [Create an app password](/docs/mail/mail-hosting/mailbox-security/#create-an-app-password). ## Find your server settings The server names depend on the region your service runs in, so copy them from the dashboard rather than typing them from memory. 1. In the dashboard, go to [**Email**](https://www.coritan.com/dashboard/mail) and open the Mail Hosting service. It opens on the **Overview** tab. 2. Find the **Mail server addresses** card. Select a name to copy it. The **Mail client setup** card below it lists the same settings in the order most apps ask for them: | Setting | Value | | --- | --- | | **Username** | The mailbox's full address, such as `alex@example.com` | | **Password** | The mailbox's password, or an app password when two-factor sign-in is on | | **Incoming (IMAP)** | The **IMAP** name, such as `imap.mail-fra.coritan.com`, port `993`, SSL/TLS | | **Outgoing (SMTP)** | The **SMTP** name, such as `smtp.mail-fra.coritan.com`, port `587`, STARTTLS, or port `465`, SSL/TLS | | **Authentication** | Normal password, with the same username and password for both servers | For an app that cannot use port `993`, IMAP also answers on port `143` with STARTTLS. The **Mail server addresses** card also lists: **JMAP** : A newer protocol for reading and sending mail over HTTPS, on port `443`. Use it only in an app that asks for a JMAP server. **Webmail** : The address of webmail, where the mailbox can sign in from a browser. For Coritan it is `https://www.coritan.com/webmail`. **MX suffix** : The name your domain's MX records point under. A mail app does not need it. ## Set up an app automatically Once a domain is verified, it carries `autoconfig` and `autodiscover` records and SRV records that point mail apps at the right servers. See [Mail DNS records](/docs/mail/dns-records/). In an app that supports automatic setup: 1. Add a new mail account. 2. Enter the mailbox's full address and its password or app password. 3. Let the app look up the settings. Check that it found the **IMAP** and **SMTP** names from the dashboard. If the app cannot find the settings, or asks for server details, set it up by hand instead. ## Set up an app by hand The labels differ between apps, but each asks for the same things. 1. Add a new mail account and choose IMAP, or the manual or advanced setup. 2. For the incoming server, enter the **IMAP** name, port `993` and SSL/TLS. 3. For the outgoing server, enter the **SMTP** name, port `587` and STARTTLS. If the app offers only SSL/TLS, use port `465`. 4. For both servers, enter the mailbox's full address as the username, the mailbox's password or an app password as the password, and normal password as the authentication method. 5. Save the account. On an iPhone, iPad or Mac, the mailbox's user can skip these steps: webmail makes a QR code that installs a profile setting up Mail and Contacts. See [Set up mail apps and devices](/docs/mail/webmail/mail-apps/). ## Result The app shows the mailbox's folders and mail, and can send from the address. When the app saves the mail it sends to the server, those messages also appear in the **Sent** folder in webmail. ## Troubleshooting The app says the username or password is wrong : Check that the username is the full address and not only the part before the @. If two-factor sign-in is on for the mailbox, the app needs an [app password](/docs/mail/mail-hosting/mailbox-security/#create-an-app-password), because it cannot send a code. A mailbox that is turned off cannot sign in; check its **Enabled** switch on the **Mailboxes** tab. The app warns about the server's certificate : The app is using a name under your own domain, such as `imap.example.com`. Enter the **IMAP** and **SMTP** names from the **Mail server addresses** card instead: the certificate covers those names. The app cannot connect to the outgoing server : Port `25` is for servers passing mail to each other, and many networks block it. Use port `587` with STARTTLS, or port `465` with SSL/TLS. The app stopped signing in after many failed attempts : After 30 failed sign-ins in an hour from one IP address, the mail server refuses that address for 24 hours. Fix the password in every app that uses the mailbox, then wait for the block to end. A message with large attachments will not send : The mail server accepts messages up to 50 MB. Mail apps encode attachments on the way out, which makes them about a third larger, so the files themselves need to add up to less. Share large files by link instead. A mail app stopped working after the password was reset : Every app that signs in with the mailbox's password needs the new one. Apps that use an app password keep working. ## Related - [Secure a mailbox](/docs/mail/mail-hosting/mailbox-security/) - [Mail DNS records](/docs/mail/dns-records/) - [Set up mail apps and devices](/docs/mail/webmail/mail-apps/) # Move existing mail into a mailbox > Copy mail into a mailbox from another server over IMAP, from Gmail or Microsoft 365, or from an mbox, eml, zip or cPanel backup file. Source: https://www.coritan.com/docs/mail/mail-hosting/move-mail-in/ In the dashboard: - /dashboard/mail/…/mailboxes: https://www.coritan.com/dashboard/mail An *import* copies mail into a mailbox from another mail server or from a file. It runs on our side, so it carries on after you close the page. Messages keep their folders, their read and flagged state and their original dates, and nothing changes on the other server. The mailbox's own user can start the same imports from webmail; see [Import mail into webmail](/docs/mail/webmail/import/). ## Before you begin - A mailbox to copy the mail into, with room for it: an import pauses when the mailbox is full. To make room, [change the mailbox's quota](/docs/mail/mail-hosting/mailboxes/#change-a-mailbox-s-quota). - For another server: the address and password of the old mailbox, and IMAP turned on there. Many providers want an *app password* made in their own settings rather than the account password; the hint under **Provider** says what each one needs. - For a file: a copy of the mail as an mbox file, a single `.eml` message, a zip or tar of mail files, or a cPanel backup. ## Import from another server 1. In the dashboard, go to [**Email**](https://www.coritan.com/dashboard/mail), open the Mail Hosting service, then the **Mailboxes** tab. 2. Open the menu at the end of the mailbox's row and select **Bring mail in…**. 3. In **Provider**, choose where the mail is now, such as `Yahoo Mail` or `iCloud Mail`. For a server that is not listed, or a cPanel host, choose `Another IMAP server` or `cPanel`, then enter its name in **Server**. 4. In **Username**, enter the old mailbox's sign-in name, usually its full address. In **Password**, enter its password or app password. 5. To copy the old Trash or Spam folders too, turn on **Also bring Trash** or **Also bring Spam**. 6. Select **Import from the server**. We sign in to the old server first, so a wrong password shows at once. The import then appears in the list under the form, and the list updates by itself while it runs. The server must offer IMAP over TLS on port `993`. We use the password only for this import and forget it when the import ends. ## Import from Gmail or Microsoft 365 by signing in When the **Provider** list offers **Gmail: sign in with Google** or **Microsoft 365 or Outlook.com: sign in with Microsoft**, you can sign in on the provider's own page instead of giving us a password. 1. In **Bring mail in**, choose one of those two in **Provider**. 2. Optionally, turn on **Also bring Trash** or **Also bring Spam**. 3. Select the button that continues to the provider, and sign in there. You come back to the **Mailboxes** tab with the message `Signed in. The import has started, and goes on with this page closed.` If the sign-in did not work, the message says why. Microsoft 365 no longer accepts passwords over IMAP, so signing in is the only way to import from it. For Gmail without signing in, choose `Gmail with an app password` and make an app password in your Google account. ## Import from a file 1. In **Bring mail in**, select a file in **Or a file**. 2. Select **Upload and import**. The button shows how much has uploaded; keep the page open until it finishes. When the upload ends, the import appears in the list. We tell what the file is from its name and first bytes: | File | What we import | | --- | --- | | mbox (`.mbox`, `.mbx`), including a Google Takeout export | Every message in it | | `.eml` | The one message | | zip or tar of mail files | Every message, keeping its folders | | cPanel backup (`backup-…` or `cpmove-…` archive) | The mailbox in the backup with the same address as this one | Mail from a file that has no folders, such as an mbox or an `.eml`, goes into a folder named `Imported`. A Google Takeout mbox is the exception: its messages go where their Gmail labels say, as they would from Gmail itself. ## Where the mail goes - The old Inbox, Sent, Drafts and Archive go into the mailbox's own folders of the same kind, and Trash and Spam too when you turned them on. We recognise them by the flags servers put on them, or by their usual names in several languages. - Any other folder is made in the mailbox with the same name and path. - From Gmail, each message is copied once and placed in every folder its labels name. Starred and Important become flags, and a message with no label goes to Archive. - We leave out messages marked deleted, messages larger than 50 MB, and messages already in the mailbox from an earlier import. ## Follow an import Each import in the list shows its source, a status, and its counts: how many messages were imported, how many were **already there**, and how many were **left out**. | Status | Meaning | | --- | --- | | **Waiting** | Queued to start. | | **Importing** | Copying mail now. | | **Paused** | Stopped for now: you paused it, the mailbox is full, or an error interrupted it. **Resume** goes on from where it stopped. | | **Done** | Every message has been copied. | | **Stopped** | It failed. The row says why, for example the old server refused the password or the file could not be read. | | **Cancelled** | You cancelled it. | When the old server does not answer, an import tries again on its own, up to five times, a few minutes apart. When Gmail stops handing over mail for the day, the import tries again every six hours until it finishes. ## Pause, resume or cancel an import - To pause a waiting or running import, select **Pause** on its row. - To go on with a paused import, select **Resume**. - To stop one for good, select **Cancel…**, then **Cancel import**. The mail already copied stays in the mailbox, and we forget the password or delete the file. Running the same import again is safe: messages already copied count as **already there** and are not copied twice. ## Troubleshooting `… refused that username and password.` : The old server did not accept the sign-in. Check the username, and whether the provider wants an app password or needs IMAP turned on first. The hint under **Provider** says which. `… did not answer` : We could not reach the server on port `993`. Check the name in **Server**, and that the server offers IMAP over TLS. `3 imports are under way into this mailbox; wait for one to end` : A mailbox takes three imports at a time, counting waiting and paused ones. Wait for one to finish, or cancel one. `Too many requests for this action. Please wait and try again.` : A mailbox takes up to 20 import attempts a day, counting those the old server refused. Try again later. `That file is not an mbox, an .eml, a zip or a tar of mail, or a cPanel backup` : We could not tell what the file is. Check that it is one of those, with its usual file name ending. `Imports take files up to … MB` : The file is larger than an import takes. Split it into smaller files, or import from the server instead. `This backup holds no mailbox …` : The cPanel backup has no mailbox with this mailbox's address. The message lists the mailboxes it does hold. Import into a mailbox with one of those addresses, or choose the one you want with the API's `cpanel_mailbox` field. The import is **Paused** and the mailbox is full : The import stops when the mailbox reaches its quota. [Raise the quota](/docs/mail/mail-hosting/mailboxes/#change-a-mailbox-s-quota) or delete mail, then select **Resume**. ## Related - [Create and manage mailboxes](/docs/mail/mail-hosting/mailboxes/) - [Add a domain to Mail Hosting](/docs/mail/mail-hosting/add-a-domain/) - [Import mail into webmail](/docs/mail/webmail/import/) ## With the API All the paths below sit under `/client/mail/{service_id}/mailboxes/{account_id}/imports`. Start an import from another server: ```bash curl -X POST https://api.coritan.com/api/v1/client/mail/4812/mailboxes/5120/imports \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"preset": "other", "host": "imap.example.net", "username": "alex@example.net", "password": "old-mailbox-password", "since": "2024-01-01"}' ``` The body takes these fields: `preset` : The provider, as an `id` from the `presets` list in `GET .../imports`: `cpanel`, `gmail`, `yahoo`, `icloud`, `zoho`, `fastmail`, `aol`, `gmx`, `yandex` or `other`. Defaults to `other`. `host` : The server's name, for `cpanel` and `other`. A preset with its own host ignores it. `username`, `password` : The old mailbox's sign-in, 1–254 and 1–512 characters. `folders` : Optional, up to 200 folder paths to import. Leave it out to import every folder. `since`, `until` : Optional dates such as `2024-01-01`. We leave out messages dated outside them. `trash`, `spam` : Optional, `false` by default. Whether to import the Trash and Spam folders. The answer is `201` with the import: ```json { "id": 77, "kind": "imap", "status": "queued", "source": {"host": "imap.example.net", "port": 993, "username": "alex@example.net", "preset": "other"}, "options": {"trash": false, "spam": false, "since": "2024-01-01"}, "imported": 0, "duplicates": 0, "skipped": 0, "failed": 0, "total": null, "error": null, "started_by": "the Mail Hosting panel", "folders_done": 0, "folders": 0, "created_at": "2026-09-16T11:20:05.201344+00:00", "started_at": null, "finished_at": null } ``` Upload a file as `multipart/form-data`: ```bash curl -X POST https://api.coritan.com/api/v1/client/mail/4812/mailboxes/5120/imports/upload \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -F "file=@backup-example.com.tar.gz" \ -F "kind=cpanel" \ -F "cpanel_mailbox=alex@example.com" ``` The form takes `file`, and optionally `kind` (`cpanel` or `eml`, when the file's name does not say), `folder` (where mail from a file with no folders goes, `Imported` by default), `cpanel_mailbox` (which mailbox of a cPanel backup to import; this mailbox's address by default), `trash` and `spam`. The answer is `201` with the import. The other operations: | Operation | Body | Answer | | --- | --- | --- | | `GET .../imports` | | `{"items": [...], "presets": [...], "max_upload_mb": ..., "oauth": {"google": ..., "microsoft": ...}}`: the last 20 imports, newest first, the providers, the largest file an upload takes in MB, and which sign-ins are available | | `POST .../imports/oauth` | `{"provider": "google", "return_path": "/dashboard/mail/4812/mailboxes"}`, with optional `since`, `until`, `trash` and `spam` | `{"url": "..."}`: send the browser there to sign in. It comes back to `return_path` on the host the request went to, which must be `www.coritan.com`, so send this one to `https://www.coritan.com/api/v1`. | | `POST .../imports/{import_id}/pause` | | The import, `paused` | | `POST .../imports/{import_id}/resume` | | The import, `queued` | | `POST .../imports/{import_id}/cancel` | | The import, `cancelled` | A refused request answers with a `detail` object that holds a `code` and a `message`: | Status | `code` | When | | --- | --- | --- | | `422` | `source_refused`, `source_unreachable` | The old server refused the sign-in or did not answer | | `422` | `unknown_preset`, `source_incomplete` | The `preset` is not one of the list, or the server, username or password is missing | | `422` | `unknown_file`, `empty_file` | We could not tell what the file is, or it is empty | | `422` | `oauth_unavailable` | Signing in to that provider is not available | | `422` | `oauth_origin` | A sign-in was started through a host other than `www.coritan.com`, such as `api.coritan.com` | | `409` | `imports_busy` | Three imports are under way into the mailbox | | `409` | `import_state` | The import's status does not allow the action, such as resuming one that is running | | `413` | `file_too_large` | The file is larger than `max_upload_mb` | An `import_id` that is not on the mailbox answers `404` `Import not found`, and the 21st attempt in a day answers `429`. ## API - `GET /api/v1/client/mail/{service_id}/mailboxes/{account_id}/imports`: Mailbox imports (https://www.coritan.com/docs/api/reference/client/mail/mail-mailboxes/#op-get-api-v1-client-mail-service-id-mailboxes-account-id-imports) - `POST /api/v1/client/mail/{service_id}/mailboxes/{account_id}/imports`: Mailbox import start (https://www.coritan.com/docs/api/reference/client/mail/mail-mailboxes/#op-post-api-v1-client-mail-service-id-mailboxes-account-id-imports) - `POST /api/v1/client/mail/{service_id}/mailboxes/{account_id}/imports/oauth`: Mailbox import sign in (https://www.coritan.com/docs/api/reference/client/mail/mail-mailboxes/#op-post-api-v1-client-mail-service-id-mailboxes-account-id-imports-oauth) - `POST /api/v1/client/mail/{service_id}/mailboxes/{account_id}/imports/upload`: Mailbox import upload (https://www.coritan.com/docs/api/reference/client/mail/mail-mailboxes/#op-post-api-v1-client-mail-service-id-mailboxes-account-id-imports-upload) - `POST /api/v1/client/mail/{service_id}/mailboxes/{account_id}/imports/{import_id}/{action}`: Mailbox import steer (https://www.coritan.com/docs/api/reference/client/mail/mail-mailboxes/#op-post-api-v1-client-mail-service-id-mailboxes-account-id-imports-import-id-action) # Change Mail Hosting settings > Set the default category for the mail your mailboxes send, set or hold each domain's DMARC stage, and remove a domain from the Settings tab. Source: https://www.coritan.com/docs/mail/mail-hosting/settings/ In the dashboard: - /dashboard/mail/…/settings: https://www.coritan.com/dashboard/mail The **Settings** tab of a Mail Hosting service holds the choices that apply across the service: the **Sending defaults**, the **DMARC** stage of each domain, and the **Danger zone**, where you remove a domain. To cancel the service itself, see [How Mail Hosting and SMTP Relay are billed](/docs/mail/billing/). ## Before you begin - A Mail Hosting service. The DMARC and **Danger zone** cards appear once the service has a domain. - For DMARC, read [How the DMARC stage ramp works](/docs/mail/dmarc/) first: it explains what each stage asks of receiving servers. ## Set the default message category Every message your mailboxes send has a *category*: *transactional* or *marketing*. A message takes its category from its `X-Mail-Category` header, and one without the header takes the service's default. Most mailboxes send person-to-person mail, which is transactional, so keep the default at **Transactional** unless the service's mailboxes send newsletters. 1. In the dashboard, go to [**Email**](https://www.coritan.com/dashboard/mail), open the Mail Hosting service, then the **Settings** tab. 2. In the **Sending defaults** card, choose a **Default message category**. The toast confirms the new default, such as `Default category is now marketing.` It applies to the next message a mailbox sends. When a marketing message has exactly one recipient and no `List-Unsubscribe` header of its own, we add the one-click unsubscribe headers that Gmail and Yahoo require on bulk mail. A message to several recipients is left as it is. See [Transactional and marketing mail](/docs/mail/smtp-relay/message-categories/). > [!IMPORTANT] > A person who unsubscribes goes on the service's suppression list, and from then on the mail server refuses mail to them from every mailbox on the service. Mail Hosting has no tab for that list, so taking someone off it needs the API. See [Manage the suppression list](/docs/mail/smtp-relay/suppressions/#with-the-api). ## Set or hold a domain's DMARC stage A domain's DMARC stage starts at `p=none` and moves up by itself as the reports show your mail passes. The **DMARC** card lists each domain with its current stage, how long it has been there, and whether it is **held** or **ramps automatically**. 1. On the **Settings** tab, find the domain in the **DMARC** card. 2. To set a stage now, choose `p=none`, `p=quarantine` or `p=reject` in the list beside it. Choosing a stage also holds it there. 3. To stop the stage moving without changing it, turn on **Hold stage**. To let it move up again, turn **Hold stage** off. The toast confirms the change, such as `DMARC for example.com set to p=quarantine.` When we publish the domain's records in a DNS zone we host, we update the DMARC record for you. When the zone is elsewhere, the domain's page shows the new record for you to publish; until you do, the domain's DMARC check fails. **Records and verification** opens the **Domains** tab. ## Remove a domain > [!WARNING] > Removing a domain stops all mail to and from its addresses, and deletes the records we published for it in a DNS zone we host. You cannot remove a domain that still has mailboxes or aliases, so this never deletes stored mail. 1. Delete the domain's mailboxes and aliases first. See [Create and manage mailboxes](/docs/mail/mail-hosting/mailboxes/#delete-a-mailbox) and [Forward addresses with aliases](/docs/mail/mail-hosting/aliases/#delete-an-alias). 2. On the **Settings** tab, in the **Danger zone** card, select **Remove domain…** beside the domain. 3. Type the domain name to confirm, then select **Remove domain**. The domain leaves the service, and its place on the plan is free again. Remove the records you published yourself in a zone elsewhere. ## Troubleshooting `The DMARC record for example.com in your DNS was not added by us. Change its p= value in the zone instead.` : The domain's zone is hosted here, but its DMARC record was there before we published ours, so we left it alone. Edit that record's `p=` value in the zone. `Remove the mailboxes and aliases on this domain first` : The domain still has at least one mailbox or alias. Delete them, then remove the domain. The stage moved up while the domain's DMARC check shows a problem : The zone is elsewhere, and the ramp moved the stage without the new record being published. Open the domain on the **Domains** tab and publish the DMARC record it shows, or choose the stage your zone publishes to hold it there. ## Related - [How the DMARC stage ramp works](/docs/mail/dmarc/) - [Add a domain to Mail Hosting](/docs/mail/mail-hosting/add-a-domain/) - [Transactional and marketing mail](/docs/mail/smtp-relay/message-categories/) ## With the API Set the service's default category: ```bash curl -X PATCH https://api.coritan.com/api/v1/client/mail/4812/category \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"default_category": "transactional"}' ``` `default_category` is `transactional` or `marketing`; any other value answers `422`. The answer is the new default: ```json {"default_category": "transactional"} ``` `GET /client/mail/{service_id}` returns it as `default_category`. To set a domain's DMARC stage, see [How the DMARC stage ramp works](/docs/mail/dmarc/#with-the-api); to remove a domain, see [Add a domain to Mail Hosting](/docs/mail/mail-hosting/add-a-domain/#with-the-api). ## API - `PATCH /api/v1/client/mail/{service_id}/category`: Set category (https://www.coritan.com/docs/api/reference/client/mail/mail/#op-patch-api-v1-client-mail-service-id-category) # How SMTP Relay works > How SMTP Relay sends your applications' mail over SMTP or HTTPS, what its plan sets, and what the Overview tab shows. Source: https://www.coritan.com/docs/mail/smtp-relay/ In the dashboard: - /dashboard/mail/…/overview: https://www.coritan.com/dashboard/mail SMTP Relay sends the mail your software writes: receipts, password resets, alerts and newsletters. Your application hands each message to the relay, and the relay signs it for your domain, delivers it and records what happened. SMTP Relay only sends. To receive mail on a domain, use [Mail Hosting](/docs/mail/mail-hosting/). ## Two ways to send Over SMTP : Point any SMTP library or application at the relay's SMTP host on port 587 (STARTTLS) or 465 (TLS), and sign in with an *SMTP credential*: a username and password the relay creates for one application. See [Send over SMTP with credentials](/docs/mail/smtp-relay/credentials/). Over HTTPS : Post a JSON message to the send API with an *API key* in the `X-Api-Key` header. See [Send email over HTTPS](/docs/mail/smtp-relay/send-with-the-api/). Both need a verified *sending domain* on the relay, and the From address must be on it. The HTTPS API sends through one of the relay's SMTP credentials, so create at least one credential even if you only use the API. ## How a message is handled 1. The relay checks the sender, the monthly allowance and the hourly limit, and drops any recipient on the [suppression list](/docs/mail/smtp-relay/suppressions/). 2. It signs the message with your domain's DKIM key and delivers it. 3. Each step becomes an *event*: accepted, delivered, deferred, bounced or complaint. You can [look events up](/docs/mail/smtp-relay/events/) for up to 90 days, or [receive them with webhooks](/docs/mail/smtp-relay/webhooks/). 4. A bounce or a spam complaint adds the address to the suppression list, so the relay stops sending to it. Each message is either *transactional* or *marketing*. Marketing mail gets one-click unsubscribe headers. See [Transactional and marketing mail](/docs/mail/smtp-relay/message-categories/). ## What the plan sets - Emails a month: the plan's allowance. Paid plans accept a buffer beyond it; the free plan stops at its allowance. The count resets on the 1st of each month. - Sending domains: a number, or unlimited. An unlimited plan still stops at 1,000 domains. - SMTP credentials: a number, or unlimited. An unlimited plan still stops at 200 credentials. - Dedicated IPs: some plans include a dedicated sending address, and a relay whose plan sends 100,000 emails a month or more can add one. The *hourly limit* is separate from the plan. Every new relay starts with a small one, and it rises as the relay sends clean mail. [How SMTP Relay sending limits work](/docs/mail/smtp-relay/sending-limits/) covers the allowance, the buffer and the hourly limit. ## What the Overview tab shows In the dashboard, go to **Email**, open the relay, then the **Overview** tab. The three figures at the top are **Emails this month** against the allowance, and the **Sending domains** and **SMTP credentials** used against the plan. Once the relay has sent 80% of its plan's emails for the month, an alert above them says how much is left; see [Sending limits](/docs/mail/smtp-relay/sending-limits/#what-happens-near-the-allowance). **Deliverability** : The bounce and complaint rates over the recent days the card names, the relay's health checks, and the **Default message category**. See [Sending reputation and deliverability](/docs/mail/smtp-relay/deliverability/). **SMTP endpoint** : The **SMTP** host name to connect to, with its ports. **Recent activity** : The latest events from the last seven days. **All events** opens the **Events** tab. **Service** : The relay's **Status**, **Plan**, **Region**, the date it **Renews**, when it was **Created** and its **Service ID**. **Tags** : Labels you add to find the relay. See [Tags](/docs/get-started/tags/). **Hourly limit** : The **Current limit**, the **Next step** it rises to, and when and how it was **Set**. **Request limit increase…** asks for more; see [Request a higher hourly limit](/docs/mail/smtp-relay/request-a-higher-limit/). **Sending IPs** : Whether the relay sends from addresses we share between customers or from dedicated addresses. Each dedicated address shows its reverse DNS name, its hourly cap, and a **Warming up** badge while it warms up. **Order dedicated IP** opens the order page for another dedicated address where the plan allows one ([Order a dedicated sending IP](/docs/mail/order-a-mail-service/#order-a-dedicated-sending-ip)). **Domains** : Each sending domain and its verification status. ## With the API Read the relay's summary: ```bash curl https://api.coritan.com/api/v1/client/smtp-relay/4812 \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` The fields you need most: `kind` : `relay` for SMTP Relay. `status` : `active` once the relay is ready. `month_to_date_sent` : Emails counted this month. `limits` : The allowance, the buffer, what is left, and the hourly limit with its next step. [Sending limits](/docs/mail/smtp-relay/sending-limits/#with-the-api) lists every field. `counts` : `relay_credential` and `domains`, the credentials and sending domains the relay holds. `default_category` : `transactional` or `marketing`, the category of a message that does not set one. `dedicated_ips` : Each dedicated address, with `address`, `ptr`, `warming_up` (`true` until its warm-up ends), `hourly_limit`, `enabled` and `addon_service_id` (set when you bought it as an add-on). `cluster` : The region the relay runs in: `region`, its code, and `region_name`, such as `Frankfurt`, or `null` when we have no name for it. `hostnames.smtp_host` : The host to connect to over SMTP. `sending_frozen`, `review_flag`, `reputation_note` : Whether sending is paused or under review, and why. ## API - `GET /api/v1/client/smtp-relay/{service_id}`: Get summary (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay/#op-get-api-v1-client-smtp-relay-service-id) # Add a sending domain to SMTP Relay > Add the domain your application sends from, publish its SPF, DKIM and DMARC records, and verify it before you send. Source: https://www.coritan.com/docs/mail/smtp-relay/add-a-sending-domain/ In the dashboard: - /dashboard/mail/…/domains: https://www.coritan.com/dashboard/mail An SMTP Relay sends only from domains you have shown are yours. Add the domain your application sends from, publish its records and verify it. The relay adds no MX record, so the domain's inbox stays wherever it is. ## Before you begin - An SMTP Relay service with the status `active`. [Order one](/docs/mail/order-a-mail-service/) if you have none. While a service is being set up, **Add domain…** is greyed out. - A domain whose DNS you can change. When its zone is on [Coritan DNS](/docs/websites/add-an-existing-domain/) in the same account, we publish the records for you. - Room on the plan. The **Domains** card shows the plan's limit, such as `1 of 2 on this plan.`, or *The domains your applications send from.* when the plan has no limit. - If the domain is on your Mail Hosting service, a sending subdomain for the relay, such as `send.example.com`. A domain can be on one mail service at a time, and the relay can still send as `@example.com` over SMTP. ## Add the domain 1. In the dashboard, go to [**Email**](https://www.coritan.com/dashboard/mail), open the SMTP Relay service, then the **Domains** tab. 2. Select **Add domain…**. 3. In **Domain**, enter the domain, such as `example.com` or `send.example.com`. Names you claimed under [coritan.gg](/docs/proxies/coritan-gg-names/) are listed beside **Your names:**. 4. Leave **Make this the primary domain** ticked if this is the relay's main domain. The primary domain names the relay in lists. 5. Select **Add domain**. The domain's page opens, with its records on the **DNS records** card: the ownership record, SPF, two DKIM records once ownership passes, and DMARC. ## Publish the records When the domain's zone is on Coritan DNS in this account, or the domain is a claimed coritan.gg name, we have already published the records. When the zone is on Coritan DNS but was added to this account after the domain, use the [**Publish to your zone**](/docs/mail/dns-records/#publish-to-your-zone) card on the domain's page. At any other DNS provider: 1. Create the ownership record: the TXT record at `_mailverify.example.com`. Copy its name and value from the **DNS records** card. 2. Create the SPF record. If the domain already has one because another service sends for it, add `include:_spf.` from the card to that record instead of creating a second one. 3. Create the DMARC record at `_dmarc.example.com`. 4. On the domain's page, select **Verify now**. Once we find the ownership record, we make the domain's two DKIM keys. 5. Reload the page, then create the two DKIM records it now lists. [Mail DNS records](/docs/mail/dns-records/) explains each record and what its check looks for. ## Verify the domain 1. On the domain's page, select **Verify now**. 2. Read the toast. `example.com is verified.` means you are done. `example.com is still pending:` is followed by the first problem the check found. 3. If it is still pending, fix what the **Checks** row and the **The last check found problems** list show, and select **Verify now** again. We also check pending domains about every ten minutes, so a domain whose records are right verifies on its own. ## Result - The domain shows `Verified` on the **Domains** tab, with **Ownership**, **SPF** and **DKIM** ticked. **DMARC** is shown too, but a domain is verified without it. - The **Send** tab and the [send API](/docs/mail/smtp-relay/send-with-the-api/) accept From addresses on the domain. - When we publish into your zone, the page shows *Ready: the records are in your zone*, with **Open Credentials**. Next, [create an SMTP credential](/docs/mail/smtp-relay/credentials/). The relay needs one for SMTP and for the send API. > [!NOTE] > Over SMTP, the relay already accepts a From address on the domain once its ownership check passes. Wait for the domain to be verified anyway: mail sent before SPF and DKIM are published fails those checks at the receiving servers. ## Remove a domain > [!WARNING] > Removing a domain stops the relay sending from it at once. If you add it again later, it gets a new ownership token and new DKIM keys, so you publish and verify it from the start. 1. Delete the SMTP credentials on the domain first. See [Send over SMTP with credentials](/docs/mail/smtp-relay/credentials/). 2. On the **Domains** tab, select the domain. 3. In the **Remove this domain** card, select **Remove domain…**. 4. Type the domain name to confirm, then select **Remove domain**. We delete the records we published into a hosted zone. Records at another DNS provider are yours to delete. ## Troubleshooting **Add domain…** is greyed out : The service is not `active` yet, or the plan's domains are all used. A full plan shows `This plan allows 2 domains. Remove one or change the plan to add another.` Remove a domain or [move to a bigger plan](/docs/mail/billing/#move-to-a-bigger-or-smaller-plan). `example.com receives mail on your Mail Hosting service. Add a sending subdomain here instead, such as send.example.com. Your relay can still send as @example.com.` : Add `send.example.com` instead. Over SMTP, the relay may still write `@example.com` in From, because the same account has proved that domain on Mail Hosting. `example.com is already on another of your mail services` : Remove it from your other SMTP Relay service first, or use a different sending subdomain. `That domain is already attached to a mail service` : Someone else's mail service has it. If the domain is yours, contact [support](/docs/support/conversations/). `This plan allows 2 domain(s)` or `A mail service may hold at most 1000 domains` : The plan, or the limit for a plan with no domain limit, is reached. Remove a domain or [move to a bigger plan](/docs/mail/billing/#move-to-a-bigger-or-smaller-plan). `MX points to` followed by other hosts : The relay does not use MX records, so this is information only: the domain can be verified with it. It shows when the domain receives its mail somewhere else. `DKIM selector cor202609r not published` : The DKIM record with that selector is missing or holds a different key. Copy the whole value from the card again. `No DKIM keys yet: the mail engine has not made them` : We have not finished making the domain's keys. Select **Verify now** again in a minute. `DMARC publishes p=reject, expected p=none` : The domain already had a DMARC record with a different stage. Keep yours and set the same stage on the domain's **DMARC policy** card, or change your record. See [DMARC](/docs/mail/dmarc/). `The zone is busy with another change. Check the domain again in a minute.` : Another change to the same zone was being saved. Select **Verify now** again. `Remove the mailboxes and aliases on this domain first` : On a relay, this means the domain still has SMTP credentials. Delete them, then remove the domain. [Add a domain to Mail Hosting](/docs/mail/mail-hosting/add-a-domain/#troubleshooting) lists the messages both products share, such as an invalid name or a platform domain. ## Related - [Mail DNS records](/docs/mail/dns-records/) - [Send over SMTP with credentials](/docs/mail/smtp-relay/credentials/) - [Send email over HTTPS](/docs/mail/smtp-relay/send-with-the-api/) - [How the DMARC stage ramp works](/docs/mail/dmarc/) ## With the API Add a sending domain: ```bash curl -X POST https://api.coritan.com/api/v1/client/smtp-relay/4812/domains \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"domain": "send.example.com"}' ``` `domain` is the name (3–253 characters) and `primary` defaults to `false`. The answer is `201` with the domain and its `records`. A name the service refuses answers `400` with one of the messages above, and a service that is not active answers `409` `Service is not active`. The other domain operations work as they do for Mail Hosting, with `/client/smtp-relay/` in the path: | Operation | What it does | | --- | --- | | `GET /client/smtp-relay/{service_id}/domains` | Lists the relay's domains as `{"items": [...]}`. | | `GET /client/smtp-relay/{service_id}/domains/{domain_id}/records` | The records to publish. See [Mail DNS records](/docs/mail/dns-records/#with-the-api). | | `POST /client/smtp-relay/{service_id}/domains/{domain_id}/verify` | Checks the domain now and answers `{"domain": {...}, "result": {...}}`. See [the example](/docs/mail/mail-hosting/add-a-domain/#with-the-api). | | `PATCH /client/smtp-relay/{service_id}/domains/{domain_id}/dmarc` | Sets or holds the DMARC stage. See [DMARC](/docs/mail/dmarc/#with-the-api). | | `DELETE /client/smtp-relay/{service_id}/domains/{domain_id}` | Removes the domain and answers `{"ok": true}`. | A domain that is not on the service answers `404` `Domain not found`. ## API - `GET /api/v1/client/smtp-relay/{service_id}/domains`: List domains (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-domains/#op-get-api-v1-client-smtp-relay-service-id-domains) - `POST /api/v1/client/smtp-relay/{service_id}/domains`: Add domain (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-domains/#op-post-api-v1-client-smtp-relay-service-id-domains) - `DELETE /api/v1/client/smtp-relay/{service_id}/domains/{domain_id}`: Remove domain (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-domains/#op-delete-api-v1-client-smtp-relay-service-id-domains-domain-id) - `PATCH /api/v1/client/smtp-relay/{service_id}/domains/{domain_id}/dmarc`: Set DMARC (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-domains/#op-patch-api-v1-client-smtp-relay-service-id-domains-domain-id-dmarc) - `GET /api/v1/client/smtp-relay/{service_id}/domains/{domain_id}/records`: Domain records (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-domains/#op-get-api-v1-client-smtp-relay-service-id-domains-domain-id-records) - `POST /api/v1/client/smtp-relay/{service_id}/domains/{domain_id}/verify`: Verify domain (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-domains/#op-post-api-v1-client-smtp-relay-service-id-domains-domain-id-verify) # Send over SMTP with credentials > Create an SMTP credential for an application, connect it to the relay on port 587 or 465, and rotate, disable or revoke it. Source: https://www.coritan.com/docs/mail/smtp-relay/credentials/ In the dashboard: - /dashboard/mail/…/credentials: https://www.coritan.com/dashboard/mail An *SMTP credential* is a username and password that an application uses to sign in to SMTP Relay. Create one for each application, so that you can rotate or revoke one without touching the others. The [send API](/docs/mail/smtp-relay/send-with-the-api/) also sends through a credential, so a relay needs at least one even if you only send over HTTPS. ## Before you begin - An SMTP Relay service with the status `active`. While the service is being set up, **Create credential…** is greyed out. - A domain on the relay whose ownership check has passed. See [Add a sending domain to SMTP Relay](/docs/mail/smtp-relay/add-a-sending-domain/). - Room for another credential. A relay holds up to 200. When its plan sets a lower limit, the **SMTP credentials** card shows it, such as `2 of 5 on this plan.` ## Create a credential 1. In the dashboard, go to [**Email**](https://www.coritan.com/dashboard/mail), open the SMTP Relay service, then the **Credentials** tab. 2. Select **Create credential…**. 3. In **Domain**, choose the domain the application sends from. A domain that is not verified yet shows `(pending)` after its name. 4. In **Label**, name the application, such as `billing app`. Leave it empty and the label is `default`. 5. Select **Create credential**. 6. Copy the **Username** and **Password** from the dialog, or select **Copy all**, and store them where the application keeps its secrets. We show the password only this once. 7. Select **I have saved them**. The username is built from the label and the domain: the label in lower case with anything other than letters and digits turned into hyphens, cut to 24 characters, then a hyphen and six random characters. A credential labelled `billing app` on `example.com` gets a username such as `billing-app-3f9a1c@example.com`. The password has 32 characters. ## Connect your application Give the application these settings. The **SMTP endpoint** card on the **Credentials** tab shows the host for your relay. | Setting | Value | | --- | --- | | Host | The **Host** on the **SMTP endpoint** card, such as `smtp.mail-fra.coritan.com` | | Port and security | `587` with STARTTLS, or `465` with implicit TLS (often called SSL/TLS) | | Authentication | `PLAIN` or `LOGIN` | | Username | The credential's full username, such as `billing-app-3f9a1c@example.com` | | Password | The credential's password | | From address | Any address on a domain of this relay, such as `receipts@example.com` | The From address does not have to be on the credential's own domain: one credential can send for every domain on the relay. To send a message as marketing mail, add the header `X-Mail-Category: marketing`; see [Transactional and marketing mail](/docs/mail/smtp-relay/message-categories/). To test the credential from a terminal, write a message to a file: ```text title="message.txt" From: Receipts To: alex@example.com Subject: Test from SMTP Relay This message came through SMTP Relay. ``` Then send it with `curl`, which upgrades the connection with STARTTLS: ```bash curl --url "smtp://smtp.mail-fra.coritan.com:587" --ssl-reqd \ --user "billing-app-3f9a1c@example.com:$SMTP_PASSWORD" \ --mail-from "receipts@example.com" \ --mail-rcpt "alex@example.com" \ --upload-file message.txt ``` In application code, use your language's SMTP library. With Python's standard library: ```python import os import smtplib from email.message import EmailMessage msg = EmailMessage() msg["From"] = "Receipts " msg["To"] = "alex@example.com" msg["Subject"] = "Your receipt" msg.set_content("Thanks for your order.") with smtplib.SMTP("smtp.mail-fra.coritan.com", 587) as smtp: smtp.starttls() smtp.login("billing-app-3f9a1c@example.com", os.environ["SMTP_PASSWORD"]) smtp.send_message(msg) ``` ## Result - The credential is listed on the **Credentials** tab with its label, its username, when its password was set and its **Enabled** switch on. - The application's messages go out signed for your domain, and each one appears on the [**Events** tab](/docs/mail/smtp-relay/events/). ## Rotate a password Rotate a credential's password when it may have leaked, or on a schedule. 1. On the **Credentials** tab, open the menu at the end of the credential's row and select **Rotate password…**. 2. Select **Rotate password** to confirm. The old password stops working at once. 3. Copy the new **Password** from the dialog, then select **I have saved them**. 4. Put the new password in the application straight away. The new password has 24 characters. The **Password set** column shows when it changed. To give a credential a password you choose, use the API: see [With the API](#with-the-api). ## Turn a credential off or on Turning a credential off stops it signing in but keeps its username and password, so you can turn it back on later. 1. On the **Credentials** tab, find the credential. 2. Use the switch in its **Enabled** column. A toast confirms the change, such as `billing-app-3f9a1c@example.com disabled.` ## Revoke a credential > [!CAUTION] > Revoking deletes the credential and its password. Any application still using it is refused from then on, and a new credential gets a different username. To pause a credential instead, turn it off. 1. On the **Credentials** tab, open the menu at the end of the credential's row and select **Revoke credential…**. 2. Type the credential's username to confirm, then select **Revoke credential**. The send API needs an active credential. It uses one on the From address's domain when there is one, and otherwise any active credential on the relay, so revoke the last one only when nothing sends through the relay. On an [internal mail tenant](/docs/mail/internal-tenants/), the credential the platform sends its own mail with can be rotated but not turned off or revoked: **Revoke credential…** shows **Staff only**. ## Troubleshooting `Prove you own example.com first: publish the ownership record shown under Domains, then check the domain` : The domain's ownership check has not passed yet. Publish the ownership record and select **Verify now** on the domain's page. See [Add a sending domain to SMTP Relay](/docs/mail/smtp-relay/add-a-sending-domain/#publish-the-records). `A relay may hold at most 200 SMTP credentials; remove one you no longer use` or `This plan allows 5 SMTP credentials` : The relay holds as many credentials as it can. Revoke one you no longer use. Several applications can share a credential, but you then rotate them together. `The domain is not registered in the mail engine yet; check the domain again in a minute` : The domain's ownership check passed moments ago and we are still setting it up. Select **Verify now** on the domain's page, then create the credential again. `Service is not active` : The service is suspended or not set up yet. Its status is on the **Overview** tab. The application says the username or password is wrong : Use the whole username, including the six random characters before the @, and the password from when you created or last rotated the credential. A credential that is turned off cannot sign in, and while sending is paused for the relay, no credential can: the panel then shows **Sending is paused**. After 30 failed sign-ins in an hour from one IP address, the relay refuses that address for 24 hours, so fix the password before the application retries many times. The application cannot sign in when its settings are a URL : Some frameworks take the settings as one URL, such as `smtp://user:password@host:587`. The username contains an @, and passwords can contain `@`, `#`, `%`, `?`, `+` and `=`, so percent-encode both, or pass the username and password as separate settings. `550 5.7.1 Sender domain example.org is not verified on this account` : The envelope sender, the address the application gives in `MAIL FROM`, is not on a domain of this relay whose ownership has been proved. Send from an address on one of the relay's domains. `550 5.7.1 From address must use a verified domain, not example.org` : The `From` header is on a domain the relay has not proved. Change the From address, or [add the domain](/docs/mail/smtp-relay/add-a-sending-domain/). `550 5.7.1 Sending is not available for this account` : The credential is turned off, or the relay is not active. Turn the credential on, or check the relay's status on the **Overview** tab. `550 5.7.1 Sending is paused for this account; contact support` : Sending is paused for the relay: its bounce or complaint rate passed our limit, or our staff paused it. See [Sending reputation and deliverability](/docs/mail/smtp-relay/deliverability/). `550 5.7.1 Monthly allowance and its 12% buffer are used up; upgrade the plan to continue` : The relay has sent everything its plan allows this month. A free plan says `Monthly sending allowance reached; upgrade the plan to continue`. See [How SMTP Relay sending limits work](/docs/mail/smtp-relay/sending-limits/). `550 5.1.1 Recipient alex@example.com is suppressed after a bounce or complaint` : The address is on the relay's suppression list. See [Manage the suppression list](/docs/mail/smtp-relay/suppressions/). `550 5.7.1 This login is not a mail account we know` : The credential belongs to a relay that no longer exists. Create a credential on an active relay. The application cannot connect : Check the host and the port. Port `587` needs STARTTLS and port `465` needs TLS from the start, so an application that uses the wrong security for the port cannot connect. Use port `587` or `465`: many networks block port `25`. A large message is refused : SMTP accepts messages up to 50 MB, including the encoded attachments, which are about a third larger than the files. The send API takes up to 25 MB. ## Related - [Add a sending domain to SMTP Relay](/docs/mail/smtp-relay/add-a-sending-domain/) - [Send email over HTTPS](/docs/mail/smtp-relay/send-with-the-api/) - [Transactional and marketing mail](/docs/mail/smtp-relay/message-categories/) - [How SMTP Relay sending limits work](/docs/mail/smtp-relay/sending-limits/) ## With the API Create a credential: ```bash curl -X POST https://api.coritan.com/api/v1/client/smtp-relay/4812/credentials \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"domain_id": 311, "label": "billing app"}' ``` The body takes these fields: `domain_id` : The domain the username goes on, from `GET /client/smtp-relay/{service_id}/domains`. Its ownership check must have passed. `label` : Optional, 1–100 characters, `default` when left out. It names the credential in lists and starts its username. The answer is `201` with the credential, its `password`, shown this once, and `smtp`, the host names to connect to: ```json { "id": 7731, "tenant_id": 903, "domain_id": 311, "kind": "relay_credential", "address": "billing-app-3f9a1c@example.com", "local_part": "billing-app-3f9a1c", "display_name": "billing app", "quota_bytes": 0, "targets": [], "status": "active", "password_set_at": "2026-09-16T10:52:03.418207+00:00", "created_at": "2026-09-16T10:52:03.418207+00:00", "totp_enabled": false, "app_passwords": 0, "frozen_reason": null, "user_id": null, "password": "Vq8#nT2mLw!4Rz=Kp7Xc9@Hd3Fs+Gy6J", "smtp": { "smtp_host": "smtp.mail-fra.coritan.com", "imap_host": "imap.mail-fra.coritan.com", "jmap_host": "jmap.mail-fra.coritan.com", "mx_hostname_base": "mail-fra.coritan.com", "webmail_url": "https://jmap.mail-fra.coritan.com", "display_name": null, "support_url": null, "postmaster_address": null, "user_mailbox_domain": null } } ``` The username is `address`, and the label is `display_name`. Connect to `smtp.smtp_host`. The other operations: | Operation | Body | Answer | | --- | --- | --- | | `GET /client/smtp-relay/{service_id}/credentials` | | `{"items": [...], "smtp": {...}}`: each credential as above with `locked` in place of `password`, and the host names | | `POST /client/smtp-relay/{service_id}/credentials/{account_id}/rotate` | | `{"id", "address", "password"}` with a new 24-character password | | `PATCH /client/smtp-relay/{service_id}/credentials/{account_id}/enabled` | `{"enabled": false}` | The credential, with `status` `disabled` or `active` | | `DELETE /client/smtp-relay/{service_id}/credentials/{account_id}` | | `{"ok": true}` | `locked` is `null` on your own services. On an internal mail tenant it holds `The platform sends its own mail with this credential. You can rotate it, but only staff can disable or delete it.`, and turning that credential off or revoking it answers `409` with the same message. To give a credential a password you choose, 12–128 characters, post it to the credential's id under `mailboxes`: ```bash curl -X POST https://api.coritan.com/api/v1/client/smtp-relay/4812/mailboxes/7731/password \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"password": "a-long-passphrase-you-chose"}' ``` The answer is `{"id", "address", "password"}` with the password you sent. The send API goes on working after a rotation or a new password, because it always uses the current one. A refused request answers `400` with one of the messages in [Troubleshooting](#troubleshooting), or `Domain not found on this service` for a `domain_id` that is not on the relay. An id that is not a credential of the relay answers `404` `Not found`, a value outside a field's range answers `422`, and a failure on our side answers `502` with a message that starts `Mail engine error:`. ### On a Mail Hosting service The same paths exist under `/client/mail/{service_id}/credentials`, where they do this: - `GET` answers `{"items": [], "smtp": {...}}`: no credentials, and the service's host names. - `POST` answers `404` `Not an SMTP Relay service`. - `.../{account_id}/rotate` and `.../{account_id}/enabled` accept a mailbox's id and act on the mailbox. `rotate` works as `POST .../mailboxes/{account_id}/password` with `{}`: it sets a generated password and turns off two-factor sign-in. See [Mailboxes](/docs/mail/mail-hosting/mailboxes/#with-the-api). - `DELETE .../{account_id}` answers `404` `Not found`. ## API - `GET /api/v1/client/smtp-relay/{service_id}/credentials`: List credentials (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-credentials/#op-get-api-v1-client-smtp-relay-service-id-credentials) - `POST /api/v1/client/smtp-relay/{service_id}/credentials`: Create credential (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-credentials/#op-post-api-v1-client-smtp-relay-service-id-credentials) - `DELETE /api/v1/client/smtp-relay/{service_id}/credentials/{account_id}`: Delete credential (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-credentials/#op-delete-api-v1-client-smtp-relay-service-id-credentials-account-id) - `PATCH /api/v1/client/smtp-relay/{service_id}/credentials/{account_id}/enabled`: Credential enabled (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-credentials/#op-patch-api-v1-client-smtp-relay-service-id-credentials-account-id-enabled) - `POST /api/v1/client/smtp-relay/{service_id}/credentials/{account_id}/rotate`: Rotate credential (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-credentials/#op-post-api-v1-client-smtp-relay-service-id-credentials-account-id-rotate) - `GET /api/v1/client/mail/{service_id}/credentials`: List credentials (https://www.coritan.com/docs/api/reference/client/mail/mail-credentials/#op-get-api-v1-client-mail-service-id-credentials) - `POST /api/v1/client/mail/{service_id}/credentials`: Create credential (https://www.coritan.com/docs/api/reference/client/mail/mail-credentials/#op-post-api-v1-client-mail-service-id-credentials) - `DELETE /api/v1/client/mail/{service_id}/credentials/{account_id}`: Delete credential (https://www.coritan.com/docs/api/reference/client/mail/mail-credentials/#op-delete-api-v1-client-mail-service-id-credentials-account-id) - `PATCH /api/v1/client/mail/{service_id}/credentials/{account_id}/enabled`: Credential enabled (https://www.coritan.com/docs/api/reference/client/mail/mail-credentials/#op-patch-api-v1-client-mail-service-id-credentials-account-id-enabled) - `POST /api/v1/client/mail/{service_id}/credentials/{account_id}/rotate`: Rotate credential (https://www.coritan.com/docs/api/reference/client/mail/mail-credentials/#op-post-api-v1-client-mail-service-id-credentials-account-id-rotate) # Create and revoke send API keys > Create an API key that sends mail through SMTP Relay over HTTPS, keep its secret safe, and revoke it when you no longer need it. Source: https://www.coritan.com/docs/mail/smtp-relay/api-keys/ In the dashboard: - /dashboard/mail/…/api-keys: https://www.coritan.com/dashboard/mail A *send API key* lets an application send mail through SMTP Relay over HTTPS instead of SMTP. The application puts the key in the `X-Api-Key` header of each request to the [send API](/docs/mail/smtp-relay/send-with-the-api/). A key can only send, and only through the relay that made it: it cannot read or change the service. These keys are separate from the keys on the **API keys** tab of **Settings**, which are described in [Manage API keys](/docs/account/api-keys/). To manage the relay itself from code, use an access token, as [Make your first API request](/docs/get-started/first-steps-with-the-api/) shows. ## Before you begin - An SMTP Relay service with the status `active`. While it is not, **Create API key…** is greyed out. - A verified sending domain and at least one [SMTP credential](/docs/mail/smtp-relay/credentials/) on the relay. You can create a key without them, but the send API refuses to send until both exist. - Fewer than 20 keys on the relay. The **API keys** card shows how many you have, such as `3 of 20 keys.` ## Create a key 1. In the dashboard, go to [**Email**](https://www.coritan.com/dashboard/mail), open the SMTP Relay service, then the **API keys** tab. 2. Select **Create API key…**. 3. In **Label**, name the application that will hold the key, such as `billing service`. Leave it empty and the label is `default`. 4. Select **Create API key**. 5. Copy the **API key** from the dialog and store it where the application keeps its secrets. We show the key only this once. 6. Select **I have saved them**. A key looks like `mk_5e0c9a7b_` followed by 43 more characters. The first part, such as `mk_5e0c9a7b`, is its *prefix*. We keep the prefix so that you can tell keys apart, and we cannot show the rest of the key again. Keep the key on your server. Anyone who has it can send mail from your domains until you revoke it, so do not put it in a web page, a mobile app or a public repository. ## Result The key is listed on the **API keys** tab with its label, its prefix, its **Abilities** (`send`), when it was **Last used** (`Never` until its first request) and when it was **Created**. The **Send over HTTPS** card below the list holds two requests to copy, **Send a message** and **Your limits**, with one of your domains in the From address. Replace `mk_...` with the key and run them. [Send email over HTTPS](/docs/mail/smtp-relay/send-with-the-api/) explains every field. ## Revoke a key > [!CAUTION] > Revoking a key cannot be undone. We refuse requests with the key from then on, so replace it in every application that uses it first. 1. On the **API keys** tab, select **Revoke…** in the key's row. 2. Type the key's prefix, such as `mk_5e0c9a7b`, to confirm, then select **Revoke key**. The key leaves the list, and a toast confirms it, such as `Key "billing service" revoked.` To give an application access again, create a new key. To replace a key without stopping the application, create the new key, put it in the application, check that its **Last used** time moves, then revoke the old one. ## Troubleshooting **Create API key…** is greyed out : The relay is not active, or it already has 20 keys and the **API keys** card shows `20 of 20 keys.` Revoke a key you no longer use. `Revoke an unused API key first (limit 20)` : The relay already has 20 keys. Revoked keys do not count, so revoke one you no longer use. `401` `Invalid API key` : The key is mistyped, cut short or revoked, or the relay is not active. Check the whole key, including the `mk_` at the start. If you no longer have it, create a new key. `401` `X-Api-Key header required` : The request has no `X-Api-Key` header. Send the key in that header: the send API does not read `Authorization`. You lost a key : We cannot show a key again. Create a new one, put it in the application, and revoke the old one. ## Related - [Send email over HTTPS](/docs/mail/smtp-relay/send-with-the-api/) - [Send over SMTP with credentials](/docs/mail/smtp-relay/credentials/) - [How SMTP Relay sending limits work](/docs/mail/smtp-relay/sending-limits/) ## With the API Create a key: ```bash curl -X POST https://api.coritan.com/api/v1/client/smtp-relay/4812/api-keys \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"label": "billing service"}' ``` `label` is optional, 1–100 characters, and `default` when left out. The answer is `201` with the key in `key`, shown this once: ```json { "id": 88, "label": "billing service", "key_prefix": "mk_5e0c9a7b", "abilities": ["send"], "last_used_at": null, "revoked_at": null, "created_at": "2026-09-16T10:52:03.418207+00:00", "key": "mk_5e0c9a7b_Q2hSbE9wN3ZrTjRaWnFMY1d4dUJ5ZzFSbTZUaEg4ZXM" } ``` The other operations: | Operation | Answer | | --- | --- | | `GET /client/smtp-relay/{service_id}/api-keys` | `{"items": [...]}`: every key that is not revoked, as above without `key` | | `DELETE /client/smtp-relay/{service_id}/api-keys/{key_id}` | `{"ok": true}`. The key stops working at once. | A key id that is not on the relay answers `404` `Not found`, and a relay with 20 keys answers `400` `Revoke an unused API key first (limit 20)`. The API creates a key while the relay is not active, but the key answers `401` until the relay is active again. ### On a Mail Hosting service Mail Hosting has no send API. Every path under `/client/mail/{service_id}/api-keys` answers `404` `Not an SMTP Relay service`. ## API - `GET /api/v1/client/smtp-relay/{service_id}/api-keys`: List API keys (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-api-keys/#op-get-api-v1-client-smtp-relay-service-id-api-keys) - `POST /api/v1/client/smtp-relay/{service_id}/api-keys`: Create API key (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-api-keys/#op-post-api-v1-client-smtp-relay-service-id-api-keys) - `DELETE /api/v1/client/smtp-relay/{service_id}/api-keys/{key_id}`: Revoke API key (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-api-keys/#op-delete-api-v1-client-smtp-relay-service-id-api-keys-key-id) - `GET /api/v1/client/mail/{service_id}/api-keys`: List API keys (https://www.coritan.com/docs/api/reference/client/mail/mail-api-keys/#op-get-api-v1-client-mail-service-id-api-keys) - `POST /api/v1/client/mail/{service_id}/api-keys`: Create API key (https://www.coritan.com/docs/api/reference/client/mail/mail-api-keys/#op-post-api-v1-client-mail-service-id-api-keys) - `DELETE /api/v1/client/mail/{service_id}/api-keys/{key_id}`: Revoke API key (https://www.coritan.com/docs/api/reference/client/mail/mail-api-keys/#op-delete-api-v1-client-mail-service-id-api-keys-key-id) # Send email over HTTPS > Send a test message from the Send tab, then send from your application with the send API and an API key. Source: https://www.coritan.com/docs/mail/smtp-relay/send-with-the-api/ In the dashboard: - /dashboard/mail/…/send: https://www.coritan.com/dashboard/mail The *send API* takes a message as JSON over HTTPS and hands it to SMTP Relay, which signs it for your domain and delivers it as it does mail sent over SMTP. Use it where an SMTP connection is awkward, such as in a serverless function. Before you wire up an application, you can check a domain end to end by sending a test message from the **Send** tab. ## Before you begin - A *verified* sending domain on the relay. Over SMTP a domain can send once its ownership check passes, but the send API waits until the domain shows `Verified`. See [Add a sending domain to SMTP Relay](/docs/mail/smtp-relay/add-a-sending-domain/). - At least one [SMTP credential](/docs/mail/smtp-relay/credentials/) on the relay. The send API submits each message with one: a credential on the From address's domain when there is one, otherwise any active credential on the relay. - A [send API key](/docs/mail/smtp-relay/api-keys/) for the application. The **Send** tab does not need one. ## Send a test message from the dashboard 1. In the dashboard, go to [**Email**](https://www.coritan.com/dashboard/mail), open the SMTP Relay service, then the **Send** tab. 2. In **From**, enter the part of the address before the @, such as `no-reply`, and choose a verified domain after it. 3. Optionally, enter a **From name**, such as `Example Billing`. 4. In **To**, enter up to 50 addresses, separated by commas or spaces. 5. Enter a **Subject** and a **Message**. The message is plain text. 6. In **Category**, leave **Service default** or choose **Transactional** or **Marketing**. See [Transactional and marketing mail](/docs/mail/smtp-relay/message-categories/). 7. Select **Send message**. A toast says `Accepted for 1 recipient.`, and a green **Accepted for delivery** box links to the **Events tab**, where you can watch the message arrive. The test message counts towards the month's allowance like any other. When the relay refuses the message, a **Could not send the message** box shows one of the messages in [Troubleshooting](#troubleshooting). ## Send from your application Post the message to `/mail/send` with the key in the `X-Api-Key` header: ```bash curl -X POST https://api.coritan.com/api/v1/mail/send \ -H "X-Api-Key: $MAIL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "receipts@example.com", "from_name": "Example Billing", "to": ["alex@example.com"], "subject": "Your receipt", "text": "Thanks for your order.", "html": "

Thanks for your order.

", "category": "transactional" }' ``` The dashboard's **API keys** tab shows the same endpoint on `www.coritan.com`. Both hosts answer it. The body takes these fields. Only `from`, `to` and one of `text` or `html` are needed: `from` : The sender's address, 3–320 characters, on a verified domain of the relay. It is used as the envelope sender too. `from_address` is accepted as another name for it. `from_name` : Optional, up to 255 characters. The name shown next to the address. `to` : A list of 1–50 addresses. `cc` : Optional, a list of up to 50 addresses. A transactional message takes 50 recipients across `to` and `cc` together. `subject` : Optional, up to 998 characters. Empty when left out. `text` : The plain-text body, up to 5,000,000 characters. `html` : The HTML body, up to 10,000,000 characters. With `text` as well, the message carries both and the recipient's app picks one. `reply_to` : Optional, up to 320 characters. The address replies go to. `headers` : Optional, an object of extra headers, such as `{"X-Order-Id": "10492"}`. Only headers whose names start with `X-`, plus `List-Unsubscribe`, `List-Unsubscribe-Post`, `List-Id` and `Precedence`, are added; any other header is dropped without an error. A name can have up to 64 characters and a value is cut at 998. `category` : Optional, `transactional` or `marketing`. Left out, the message takes the relay's default category. The send API has no field for attachments or blind copies, and it ignores fields it does not know, such as `bcc` or `attachments`. To send either, use [SMTP](/docs/mail/smtp-relay/credentials/). The whole message, as built, can be up to 25 MB. We add `Date`, a `Message-ID` at your domain and an `X-Mail-Category` header that names the category we sent the message as. ## Read the answer The answer is `202`: we accepted the message and queued it for delivery. It does not mean the message has arrived. Follow it on the [**Events** tab](/docs/mail/smtp-relay/events/) or with [webhooks](/docs/mail/smtp-relay/webhooks/). A transactional message answers with the addresses we sent it to: ```json { "accepted": ["alex@example.com"], "suppressed": [], "node": "mx1.mail-fra.coritan.com", "message_id": "<175890432171.2481.9311874401294517206@example.com>" } ``` `accepted` : The recipients we queued the message for. `suppressed` : The recipients we left out because they are on the relay's [suppression list](/docs/mail/smtp-relay/suppressions/). When every recipient is suppressed, nothing is sent, `accepted` is empty and `node` is `null`, and the answer is still `202`. `node` : The name of the mail server that took the message. `message_id` : The message's `Message-ID` header. Webhook events and receiving servers refer to the message by it. ## Send marketing mail With `"category": "marketing"`, or no category on a relay whose default is marketing, the send API sends each address in `to` and `cc` its own copy, addressed to that person alone. Each copy carries `List-Unsubscribe` and `List-Unsubscribe-Post` headers with a link for that recipient, so receiving apps can show an unsubscribe button. [Transactional and marketing mail](/docs/mail/smtp-relay/message-categories/) explains what an unsubscribe does. The answer counts the copies and lists each one: ```json { "accepted": 2, "category": "marketing", "messages": [ { "accepted": ["alex@example.com"], "suppressed": [], "node": "mx1.mail-fra.coritan.com", "message_id": "<175890432171.2481.9311874401294517206@example.com>" }, { "accepted": ["sam@example.com"], "suppressed": [], "node": "mx1.mail-fra.coritan.com", "message_id": "<175890432172.2481.5170284763319902451@example.com>" } ] } ``` Here `accepted` is a number, the copies we queued, where a transactional answer gives a list of addresses. A copy to a suppressed address appears in `messages` with an empty `accepted` and is not counted. ## Check your limits Read what the relay may still send with the same key: ```bash curl https://api.coritan.com/api/v1/mail/send/limits \ -H "X-Api-Key: $MAIL_API_KEY" ``` The answer holds the relay's allowance and hourly limit, which [How SMTP Relay sending limits work](/docs/mail/smtp-relay/sending-limits/#with-the-api) describes field by field, and three more fields: `sending_frozen` : `true` while sending is paused for the relay. `max_recipients` : `50`, the most recipients one request can have. `max_message_bytes` : `26214400`, the largest message the send API builds, in bytes (25 MB). Check `remaining` before a large batch: it is `null` when the relay has no monthly cap. ## Troubleshooting `401` `X-Api-Key header required` : The request has no `X-Api-Key` header. The send API does not read `Authorization`. `401` `Invalid API key` : The key is wrong or revoked, or the relay is not active. See [Create and revoke send API keys](/docs/mail/smtp-relay/api-keys/#troubleshooting). `403` `API key cannot send` : The key does not have the `send` ability. Keys you create on the **API keys** tab always have it, so create a new key. `422` with a `detail` list : A field is missing or out of range: no `from` or `to`, more than 50 addresses in `to` or `cc`, or a `category` other than `transactional` or `marketing`. Each item in `detail` names the field in `loc`. `400` `Provide a text or html body` : Add `text`, `html` or both. `400` `At most 50 recipients per message` : A transactional message has more than 50 addresses in `to` and `cc` together. Split it into several requests. `400` `Message exceeds 25 MB` : Shorten the message, or link to large content instead of putting it in the body. `400` `example.org is not a domain on this service` : The domain of `from` is not on the relay. Use an address on one of the relay's domains, or [add the domain](/docs/mail/smtp-relay/add-a-sending-domain/). A subdomain is a domain of its own: with only `send.example.com` on the relay, send from an address at `send.example.com`. Over SMTP, the From header can use `example.com` when your account also holds that domain, in Coritan DNS or on another mail service; the send API cannot. `400` `example.com is not verified yet; publish its DNS records first` : The domain is on the relay but not verified. Publish its records and verify it. `400` `Create an SMTP credential on this service before sending through the API` : The relay has no active SMTP credential. [Create one](/docs/mail/smtp-relay/credentials/#create-a-credential), or turn one back on. `400` `Sending is paused on this service; contact support` : Sending is paused for the relay. See [Sending reputation and deliverability](/docs/mail/smtp-relay/deliverability/). `429` `Monthly allowance and its 12% buffer are used up; upgrade the plan to continue` : Sending the request would take the relay past everything its plan allows this month. A free plan says `Monthly sending allowance reached; upgrade the plan to continue`. See [How SMTP Relay sending limits work](/docs/mail/smtp-relay/sending-limits/). `400` `Rejected by mail engine:` followed by an SMTP reply : The relay's mail server refused the message, and the reply after the colon says why. The replies it can give are listed under [SMTP credentials](/docs/mail/smtp-relay/credentials/#troubleshooting). `400` `No mail node is available right now` or `400` `All mail nodes refused the connection:` : We could not reach a mail server to take the message. Nothing was sent, so retry the request after a short wait. `503` `Mail service unavailable` : The relay's service could not be found on our side. Contact [support](/docs/support/conversations/) if it persists. ## Related - [Create and revoke send API keys](/docs/mail/smtp-relay/api-keys/) - [Transactional and marketing mail](/docs/mail/smtp-relay/message-categories/) - [Look up message events](/docs/mail/smtp-relay/events/) - [Mail Send API reference](/docs/api/reference/client/mail-send-api/) ## With the API The **Send** tab posts to `/client/smtp-relay/{service_id}/messages`, which takes the same body and gives the same answers with an access token in place of an API key: ```bash curl -X POST https://api.coritan.com/api/v1/client/smtp-relay/4812/messages \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"from": "no-reply@example.com", "to": ["alex@example.com"], "subject": "Test", "text": "Hello from SMTP Relay."}' ``` It answers `202` as above. A relay that is not active answers `409` `Service is not active`. On a Mail Hosting service, `POST /client/mail/{service_id}/messages` answers `404` `Not an SMTP Relay service`. Mailboxes send through [a mail app or webmail](/docs/mail/mail-hosting/connect-a-mail-app/). ## API - `POST /api/v1/mail/send`: Send (https://www.coritan.com/docs/api/reference/client/mail-send-api/#op-post-api-v1-mail-send) - `GET /api/v1/mail/send/limits`: Limits (https://www.coritan.com/docs/api/reference/client/mail-send-api/#op-get-api-v1-mail-send-limits) - `POST /api/v1/client/smtp-relay/{service_id}/messages`: Send message (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay/#op-post-api-v1-client-smtp-relay-service-id-messages) - `POST /api/v1/client/mail/{service_id}/messages`: Send message (https://www.coritan.com/docs/api/reference/client/mail/mail/#op-post-api-v1-client-mail-service-id-messages) # Transactional and marketing mail > How the message category changes what the relay adds to each message, how one-click unsubscribe works, and how to set the default. Source: https://www.coritan.com/docs/mail/smtp-relay/message-categories/ Every message SMTP Relay sends is either *transactional* or *marketing*. The category decides whether the relay adds *one-click unsubscribe* headers, which let the recipient's mail app show an unsubscribe button. Gmail and Yahoo ask for those headers on bulk mail. Transactional : Mail a person expects because of something they did: receipts, alerts, password resets, sign-in codes. The relay sends it as your application wrote it. Marketing : Newsletters, announcements and campaigns. The relay adds unsubscribe headers, and over the send API it sends each recipient a separate copy. ## How a message gets its category The relay reads the first of these that is set: 1. Over the [send API](/docs/mail/smtp-relay/send-with-the-api/), the `category` field: `transactional` or `marketing`. The API refuses any other value. 2. Over [SMTP](/docs/mail/smtp-relay/credentials/), the header `X-Mail-Category: marketing` or `X-Mail-Category: transactional`. Case does not matter. 3. The relay's *default category*, which starts as transactional. Over SMTP, an `X-Mail-Category` header with any other value, such as `newsletter`, makes the message transactional, whatever the default. ## What marketing adds Each marketing message gets two headers: ```text List-Unsubscribe: List-Unsubscribe-Post: List-Unsubscribe=One-Click ``` The link points at our API. Its `{token}` is signed, and names the relay and the one recipient the copy is for. Because the link belongs to one person, how the relay adds it depends on how the message arrives. Over the send API : The relay sends each address in `to` and `cc` its own copy, addressed to that person alone, with its own link. The headers replace any `List-Unsubscribe` pair you put in `headers`. The message also carries `X-Mail-Category: marketing`. Over SMTP : The relay adds the headers only to a message with exactly one recipient and no `List-Unsubscribe` header of its own. A message with several recipients goes out as it is, without unsubscribe headers, because one link cannot name them all. For bulk mail over SMTP, send one message per recipient, or add your own `List-Unsubscribe` headers. A transactional message gets no unsubscribe headers from the relay. Over the send API it carries `X-Mail-Category: transactional`, and any `List-Unsubscribe` pair you add in `headers` is kept. ## What an unsubscribe does When the recipient selects the unsubscribe button, their mail app posts to the link, as RFC 8058 describes. Someone who opens the link in a browser gets the same result. Either way: - We add the address to the relay's [suppression list](/docs/mail/smtp-relay/suppressions/) with the reason `unsubscribe`. - The page says `alex@example.com will not receive further marketing email from this sender.` - Selecting the link again changes nothing. > [!IMPORTANT] > A suppressed address receives nothing from the relay, transactional mail included, even though the page mentions only marketing email. The relay drops it from every message sent over the API and refuses it as a recipient over SMTP. If the person later needs a receipt or a password reset, [remove them from the suppression list](/docs/mail/smtp-relay/suppressions/#remove-an-address) once they ask to hear from you again. The unsubscribe covers the whole relay, every sending domain included. A link does not expire. ## Set the default category The default applies to every message that does not set a category. On the relay's **Overview** tab, in the **Deliverability** card, choose **Default message category**. A toast confirms the change, such as `Default category is now marketing.` It applies to messages sent from then on. Set it to marketing only when most of what the relay sends is bulk mail, and mark receipts and other transactional messages as `transactional` one by one. A Mail Hosting service has its own default, on its **Settings** tab: see [Mail Hosting settings](/docs/mail/mail-hosting/settings/#set-the-default-message-category). ## With the API Set the relay's default category: ```bash curl -X PATCH https://api.coritan.com/api/v1/client/smtp-relay/4812/category \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"default_category": "marketing"}' ``` `default_category` is `transactional` or `marketing`; any other value answers `422`. The answer is the new default: ```json {"default_category": "marketing"} ``` The unsubscribe link is `GET` or `POST /mail/unsubscribe/{token}`. It needs no key or token of yours, ignores the request body, and answers `200` with the plain-text sentence above. A link that has been changed or cut short answers `404` `Unknown unsubscribe link`. Your application does not call it: receiving mail apps and recipients do. ## API - `PATCH /api/v1/client/smtp-relay/{service_id}/category`: Set category (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay/#op-patch-api-v1-client-smtp-relay-service-id-category) - `GET /api/v1/mail/unsubscribe/{token}`: RFC 8058 target (https://www.coritan.com/docs/api/reference/client/mail-send-api/#op-get-api-v1-mail-unsubscribe-token) - `POST /api/v1/mail/unsubscribe/{token}`: RFC 8058 target (https://www.coritan.com/docs/api/reference/client/mail-send-api/#op-post-api-v1-mail-unsubscribe-token) # Receive delivery events with webhooks > Add a webhook URL that receives delivery, bounce and complaint events, and check the signature on each request. Source: https://www.coritan.com/docs/mail/smtp-relay/webhooks/ In the dashboard: - /dashboard/mail/…/webhooks: https://www.coritan.com/dashboard/mail A *webhook* is an HTTPS address in your application that we send a request to each time something happens to a message the relay sends: we queue it, the receiving server takes it or refuses it, or the recipient complains about it. Use webhooks to react to bounces and complaints as they happen. Each request carries one event as JSON, signed with a secret that only you and we hold, so your application can check that the request came from us. ## Before you begin - An SMTP Relay service with the status `active`. While it is not, **Add webhook…** is greyed out. - An endpoint in your application that accepts `POST` requests at an `https://` URL reachable from the internet, with a valid certificate from a public certificate authority. We do not follow redirects, so use the final URL. - Fewer than 10 webhooks on the relay. A webhook we have turned off still counts. ## Add a webhook 1. In the dashboard, go to [**Email**](https://www.coritan.com/dashboard/mail), open the SMTP Relay service, then the **Webhooks** tab. 2. Select **Add webhook…**. 3. In **Endpoint URL**, enter the address of your endpoint, such as `https://app.example.com/hooks/mail`. 4. Under **Events**, clear the events you do not want. Every event is selected at first, and [Events](#events) describes each one. 5. Select **Add webhook**. 6. Copy the **Signing secret** from the dialog and store it where the application keeps its secrets. We show it only this once. 7. Select **I have saved them**. ## Result The webhook is listed on the **Webhooks** tab with its URL, the events it receives, its **Status** and when it was **Created**. Its status is **Healthy** until a request to it fails. The webhook receives the events we record from then on, and none from before. Requests go out about once a minute, so expect each one a minute or so after its event. ## Events | Event | What happened | `type` values | | --- | --- | --- | | `accepted` | We queued the message for delivery. | `queue.authenticated-message-queued`, `queue.message-queued` | | `delivered` | The receiving server took the message. | `delivery.delivered`, `delivery.dsn-success` | | `deferred` | The receiving server refused the message for now, or a sending limit held it back. We try again later. | `delivery.dsn-temp-fail`, `delivery.rate-limit-exceeded`, `queue.rate-limit-exceeded`, `queue.quota-exceeded` | | `bounced` | The message was not delivered and we will not try again. We add the recipient to the [suppression list](/docs/mail/smtp-relay/suppressions/). | `delivery.failed`, `delivery.dsn-perm-fail`, `delivery.double-bounce` | | `complaint` | A report about the message reached us: the recipient marked it as spam, or a mailbox provider reported it as fraud or as failing authentication. We add the recipient to the suppression list. | `incoming-report.abuse-report`, `incoming-report.fraud-report`, `incoming-report.auth-failure-report` | The dialog also offers `suppressed`, described as "Dropped: the address is on the suppression list", but we never send a request for it. Over SMTP the relay refuses a suppressed address with `550 5.1.1`, and the send API leaves it out and lists it in the `suppressed` field of its answer. A message the relay refuses when your application submits it, such as one from a domain that is not verified, produces no request either. Your application sees the refusal as the SMTP reply or the API error. ## The request Each request is a `POST` with `Content-Type: application/json` and one event in the body. It carries two headers of its own: `X-Mail-Signature` : The HMAC-SHA256 of the raw body, made with the webhook's signing secret and written as lower-case hex. [Verify the signature](#verify-the-signature) shows how to check it. `X-Mail-Event` : The event, the same as `event` in the body, such as `delivered`. The body is compact JSON with its keys in alphabetical order, exactly as we signed it: ```json {"event":"delivered","from":"receipts@example.com","id":90412,"message_id":"<175890432171.2481.9311874401294517206@example.com>","occurred_at":"2026-09-16T10:52:08","queue_id":"7d2c91a04e","response":"250 2.0.0 OK","to":"alex@example.com","type":"delivery.delivered"} ``` `id` : The event's id, a number. It stays the same on every attempt to send the event, so use it to spot a repeat. `event` : The event: `accepted`, `delivered`, `deferred`, `bounced` or `complaint`. `type` : The detailed event the mail server recorded, one of the `type` values in [Events](#events). `queue_id` : Our id for the message in the delivery queue, the same on every event about that message. `message_id` : The message's `Message-ID` header. `from` : The sender address, in lower case. `to` : The recipient address, in lower case. An event about the whole message, such as `accepted`, names only its first recipient. `response` : The receiving server's reply or the reason for the event, up to 2,000 characters. `occurred_at` : When the event happened, in UTC, with no time zone suffix. Any field except `id`, `event`, `type` and `occurred_at` is `null` when the event does not carry it, so do not assume that every event has every field. The **Payload** card on the **Webhooks** tab sums up the method, the signature and the fields. ## Verify the signature Check the signature before you act on a request: 1. Read the raw body as bytes, before a JSON parser touches it. A parser that reads the JSON and writes it out again can change the bytes, and then the signature no longer matches. 2. Compute the HMAC-SHA256 of those bytes with the signing secret as the key. Use the secret as text, exactly as we showed it: do not decode it. 3. Write the result as lower-case hex and compare it with `X-Mail-Signature`, using a comparison that takes the same time whatever the input. 4. When they differ, answer `401` and ignore the body. In Python with Flask: ```python import hashlib import hmac import os from flask import Flask, abort, request app = Flask(__name__) SECRET = os.environ["MAIL_WEBHOOK_SECRET"].encode() @app.post("/hooks/mail") def mail_event(): body = request.get_data() expected = hmac.new(SECRET, body, hashlib.sha256).hexdigest().encode() given = request.headers.get("X-Mail-Signature", "").encode() if not hmac.compare_digest(expected, given): abort(401) handle(request.get_json()) return "", 204 ``` In Node.js with Express: ```javascript import crypto from "node:crypto"; import express from "express"; const app = express(); const secret = process.env.MAIL_WEBHOOK_SECRET; app.post("/hooks/mail", express.raw({ type: "application/json" }), (req, res) => { const expected = Buffer.from(crypto.createHmac("sha256", secret).update(req.body).digest("hex")); const given = Buffer.from(req.get("X-Mail-Signature") || ""); if (given.length !== expected.length || !crypto.timingSafeEqual(given, expected)) { return res.sendStatus(401); } handle(JSON.parse(req.body.toString("utf8"))); res.sendStatus(204); }); app.listen(3000); ``` In both, `handle` stands for your own code. [Answer the request](#answer-the-request) says how long it may take. The signature covers the body alone. The body holds `occurred_at`, so you can refuse an old event, and `id`, so you can refuse one you have already handled. ## Answer the request Answer with any `2xx` status within 10 seconds. We count everything else as a failure: another status, a redirect, a timeout, a refused connection or a certificate we cannot verify. When your handling takes longer, store the event, answer, and process the event afterwards. After a failed attempt we send the same event again, up to six attempts in all. We wait about 2 minutes before the second attempt, then about 4, 8, 16 and 32 minutes before each of the next four. When the sixth attempt fails, we stop sending that event to the webhook. It is still on the [**Events** tab](/docs/mail/smtp-relay/events/). Because of retries, the same event can arrive more than once, and events can arrive in a different order from the one they happened in. Use `id` to ignore a repeat and `occurred_at` to put events in order. ### When we turn a webhook off Every failed attempt, for any event, adds one to the webhook's count of failures in a row, and a request that succeeds sets the count back to zero. At 50 failures in a row we turn the webhook off: we drop the events waiting for it and send it nothing more. A relay that sends a lot can reach 50 within minutes of an outage on your side, because each waiting event counts on its own. The **Status** column shows where each webhook stands: **Healthy** : The last request succeeded, or we have not sent one yet. `3 failing` : The last three attempts failed. **Disabled** : We turned the webhook off after 50 failures in a row. Under the status, a line such as `Last HTTP 503 · 12 minutes ago` gives the status code your endpoint last answered with and the time since the last request that succeeded. You cannot turn a webhook back on. Fix the endpoint, then delete the webhook and add it again. The new webhook has a new signing secret, and we do not send the events from the time the old one was off. ## Replace the signing secret We cannot show a signing secret again or change it. To move to a new secret without missing events: 1. Add a second webhook with the same URL and events, and save its signing secret. 2. Make your endpoint accept a signature made with either secret. 3. Delete the old webhook. 4. Remove the old secret from your endpoint. While both webhooks exist, each event arrives twice with the same `id`, once from each webhook. ## Delete a webhook > [!WARNING] > Deleting a webhook cannot be undone. We stop sending to it at once and drop the events waiting for it. 1. On the **Webhooks** tab, select **Delete…** in the webhook's row. 2. Type `delete` to continue, then select **Delete webhook**. The webhook leaves the list, and a toast confirms `Webhook deleted.` ## Troubleshooting **Add webhook…** is greyed out : The relay's status is not `active`. `Enter a URL starting with https://.` : The address in **Endpoint URL** does not start with `https://` in lower case. We send events only over HTTPS. `Choose at least one event.` : Every event is cleared. Select at least one under **Events**. `Limit of 10 webhooks per service` : The relay already has 10 webhooks. Webhooks we have turned off count, so delete one you no longer use. **Disabled** : Fifty attempts in a row failed, so we turned the webhook off. Fix the endpoint, then delete the webhook and add it again. The signature does not match : Compute it over the raw body before you parse the JSON, use the secret as text exactly as we showed it, and compare lower-case hex. Each webhook has its own secret, so use the one we showed when you added this webhook. No requests arrive : Check that the webhook's events include the one you expect, that the endpoint is reachable from the internet over HTTPS with a valid certificate, and that it does not answer with a redirect. Requests go out about once a minute, and a new webhook receives only events we record after you add it. You lost the signing secret : We cannot show it again. Follow [Replace the signing secret](#replace-the-signing-secret). ## Related - [Look up message events](/docs/mail/smtp-relay/events/) - [Manage the suppression list](/docs/mail/smtp-relay/suppressions/) - [Send email over HTTPS](/docs/mail/smtp-relay/send-with-the-api/) - [SMTP Relay webhooks API reference](/docs/api/reference/client/mail/smtp-relay-webhooks/) ## With the API Add a webhook: ```bash curl -X POST https://api.coritan.com/api/v1/client/smtp-relay/4812/webhooks \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"url": "https://app.example.com/hooks/mail", "events": ["bounced", "complaint"]}' ``` `url` : 8–1,024 characters, starting with `https://`. `events` : Optional. The events to send, from `accepted`, `delivered`, `deferred`, `bounced`, `complaint` and `suppressed`. We drop any other name. An empty list, or one with no name we know, means all six. The answer is `201` with the signing secret in `secret`, shown this once: ```json { "id": 214, "url": "https://app.example.com/hooks/mail", "events": ["bounced", "complaint"], "enabled": true, "failure_count": 0, "last_status": null, "last_delivered_at": null, "created_at": "2026-09-16T10:41:27.318204+00:00", "secret": "3kQ9vR2xT7mW1pL5nH8cJ4bF6gD0sZyAeUoIiKqNtMw" } ``` `enabled` is `false` once we have turned the webhook off. `failure_count` is its count of failures in a row, `last_status` is the status code your endpoint last answered with, and `last_delivered_at` is when a request to it last succeeded, in UTC. The other operations: | Operation | Answer | | --- | --- | | `GET /client/smtp-relay/{service_id}/webhooks` | `{"items": [...]}`: every webhook, including ones we have turned off, as above without `secret` | | `DELETE /client/smtp-relay/{service_id}/webhooks/{webhook_id}` | `{"ok": true}`. We stop sending to the webhook at once. | No operation turns a webhook back on or changes its URL, events or secret. A URL that does not start with `https://` answers `400` `Webhook URLs must use https://`, a new webhook on a relay that has 10 answers `400` `Limit of 10 webhooks per service`, and a webhook id that is not on the relay answers `404` `Not found`. ### On a Mail Hosting service Mail Hosting has no **Webhooks** tab, but the same operations work under `/client/mail/{service_id}/webhooks`. A webhook there receives the events we record for mail the service's mailboxes send. Some bounces reach the sending mailbox only as an `Undelivered Mail Returned to Sender` message, so a webhook there does not see every bounce. ## API - `GET /api/v1/client/smtp-relay/{service_id}/webhooks`: List webhooks (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-webhooks/#op-get-api-v1-client-smtp-relay-service-id-webhooks) - `POST /api/v1/client/smtp-relay/{service_id}/webhooks`: Create webhook (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-webhooks/#op-post-api-v1-client-smtp-relay-service-id-webhooks) - `DELETE /api/v1/client/smtp-relay/{service_id}/webhooks/{webhook_id}`: Delete webhook (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-webhooks/#op-delete-api-v1-client-smtp-relay-service-id-webhooks-webhook-id) - `GET /api/v1/client/mail/{service_id}/webhooks`: List webhooks (https://www.coritan.com/docs/api/reference/client/mail/mail-webhooks/#op-get-api-v1-client-mail-service-id-webhooks) - `POST /api/v1/client/mail/{service_id}/webhooks`: Create webhook (https://www.coritan.com/docs/api/reference/client/mail/mail-webhooks/#op-post-api-v1-client-mail-service-id-webhooks) - `DELETE /api/v1/client/mail/{service_id}/webhooks/{webhook_id}`: Delete webhook (https://www.coritan.com/docs/api/reference/client/mail/mail-webhooks/#op-delete-api-v1-client-mail-service-id-webhooks-webhook-id) # Manage the suppression list > See which addresses the relay no longer sends to and why, add an address yourself, and remove one to send to it again. Source: https://www.coritan.com/docs/mail/smtp-relay/suppressions/ In the dashboard: - /dashboard/mail/…/suppressions: https://www.coritan.com/dashboard/mail The *suppression list* holds the addresses that an SMTP Relay service no longer sends to. We add an address when a message to it bounces, when its recipient complains about a message, or when they unsubscribe from marketing mail, and you can add addresses yourself. Each relay has its own list. A suppression covers every message the relay sends, over SMTP and over the send API, transactional and marketing alike. It lasts until you remove the address. ## Before you begin - An SMTP Relay service. To add an address on the **Suppressions** tab, the relay's status must be `active`; while it is not, the **Address** field is greyed out. ## Why an address is on the list The **Reason** column says how each address got there: | **Reason** | `reason` in the API | When we add the address | | --- | --- | --- | | **Hard bounce** | `bounce` | A message to it was not delivered and we stopped trying: the `bounced` event. | | **Spam complaint** | `complaint` | A report about a message to it reached us: the `complaint` event. | | **Unsubscribed** | `unsubscribe` | The recipient used the unsubscribe link in a [marketing message](/docs/mail/smtp-relay/message-categories/). | | **Added by you** | `manual` | You added it on the **Suppressions** tab or through the API. | **Since** shows how long ago the address was added. [Receive delivery events with webhooks](/docs/mail/smtp-relay/webhooks/#events) says when each event happens. We match the whole address and ignore case, so `Alex@Example.com` is the same entry as `alex@example.com`, while `alex+news@example.com` is a different one. ## What a suppression does When a message goes to a suppressed address: - Over SMTP, the relay refuses that recipient with `550 5.1.1 Recipient alex@example.com is suppressed after a bounce or complaint` and accepts the others. The reply names a bounce or complaint whatever the reason, including for an address you added. - Through the send API, we leave the address out, send the message to the other recipients, and list the address in the `suppressed` field of the answer. [Send email over HTTPS](/docs/mail/smtp-relay/send-with-the-api/#read-the-answer) shows the answer. Neither case produces a webhook request. A refusal over SMTP can appear on the [**Events** tab](/docs/mail/smtp-relay/events/) as `rejected`. ## Add an address 1. In the dashboard, go to [**Email**](https://www.coritan.com/dashboard/mail), open the SMTP Relay service, then the **Suppressions** tab. 2. Under **Suppress an address**, enter the address in **Address**, such as `alex@example.com`. 3. Select **Suppress address**. ## Result A toast confirms it, such as `alex@example.com suppressed.`, and the address is listed with the reason **Added by you**. From then on the relay refuses to send to it. When the address is already on the list, the toast says `alex@example.com is already suppressed.` and the entry keeps its reason and date. ## Find an address The list shows the 200 addresses added most recently, newest first. An older entry still applies, but it does not appear on the tab or in the API. To narrow the list, type in the search field above it. It matches part of an address, or a reason as the API names it, such as `bounce` or `unsubscribe`. ## Remove an address > [!WARNING] > An address that bounced or complained is likely to do so again. Each bounce and complaint counts against the relay's reputation, and a relay whose rates get too high has its sending paused. [Sending reputation and deliverability](/docs/mail/smtp-relay/deliverability/) explains the thresholds. 1. On the **Suppressions** tab, find the address and select **Release…** in its row. 2. Select **Release address**. A toast confirms it, such as `alex@example.com released.` The relay sends to the address again straight away. If a new message to it bounces or draws a complaint, we add it back. For someone who unsubscribed, remove the address only once they have asked to hear from you again. ## Troubleshooting `550 5.1.1 Recipient alex@example.com is suppressed after a bounce or complaint` : The address is on the relay's suppression list, for any of the reasons above. Remove it if you should mail it again. `Enter an email address, such as someone@example.com.` : The text in **Address** is not an email address. Enter the whole address. The **Address** field is greyed out : The relay's status is not `active`. An address you expect is not in the list : The list shows only the 200 most recent entries, and the search looks only through those. An older entry still applies. Adding the address again tells you whether it is on the list: the toast says `is already suppressed` when it is. ## Related - [Receive delivery events with webhooks](/docs/mail/smtp-relay/webhooks/) - [Look up message events](/docs/mail/smtp-relay/events/) - [Transactional and marketing mail](/docs/mail/smtp-relay/message-categories/) - [SMTP Relay suppressions API reference](/docs/api/reference/client/mail/smtp-relay-suppressions/) ## With the API List the addresses: ```bash curl https://api.coritan.com/api/v1/client/smtp-relay/4812/suppressions \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "items": [ {"id": 5120, "address": "alex@example.com", "reason": "bounce", "created_at": "2026-09-16T10:52:08"}, {"id": 5087, "address": "sam@example.net", "reason": "manual", "created_at": "2026-09-12T08:15:40"} ] } ``` The list holds the 200 entries added most recently, newest first, and takes no paging parameters. `reason` is `bounce`, `complaint`, `unsubscribe` or `manual`, and `created_at` is in UTC. Add an address: ```bash curl -X POST https://api.coritan.com/api/v1/client/smtp-relay/4812/suppressions \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"address": "alex@example.com"}' ``` `address` is 3–320 characters. We store it in lower case, without surrounding spaces, with the reason `manual`. The answer is `201` `{"ok": true, "added": true}`. `added` is `false`, and nothing changes, when the address is already on the list or has no `@`. Unlike the tab, the API adds an address while the relay is not active. Remove an address by its `id` from the list: ```bash curl -X DELETE https://api.coritan.com/api/v1/client/smtp-relay/4812/suppressions/5120 \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` The answer is `{"ok": true}`, or `404` `Not found` when the id is not on the relay. ### On a Mail Hosting service A Mail Hosting service keeps a suppression list too, filled the same way, and the mail server refuses to send to an address on it from any of the service's mailboxes, with the same `550 5.1.1` reply. The service has no **Suppressions** tab, so use the same three operations under `/client/mail/{service_id}/suppressions` to see, add and remove addresses. ## API - `GET /api/v1/client/smtp-relay/{service_id}/suppressions`: List suppressions (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-suppressions/#op-get-api-v1-client-smtp-relay-service-id-suppressions) - `POST /api/v1/client/smtp-relay/{service_id}/suppressions`: Add suppression (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-suppressions/#op-post-api-v1-client-smtp-relay-service-id-suppressions) - `DELETE /api/v1/client/smtp-relay/{service_id}/suppressions/{suppression_id}`: Remove suppression (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-suppressions/#op-delete-api-v1-client-smtp-relay-service-id-suppressions-suppression-id) - `GET /api/v1/client/mail/{service_id}/suppressions`: List suppressions (https://www.coritan.com/docs/api/reference/client/mail/mail-suppressions/#op-get-api-v1-client-mail-service-id-suppressions) - `POST /api/v1/client/mail/{service_id}/suppressions`: Add suppression (https://www.coritan.com/docs/api/reference/client/mail/mail-suppressions/#op-post-api-v1-client-mail-service-id-suppressions) - `DELETE /api/v1/client/mail/{service_id}/suppressions/{suppression_id}`: Remove suppression (https://www.coritan.com/docs/api/reference/client/mail/mail-suppressions/#op-delete-api-v1-client-mail-service-id-suppressions-suppression-id) # Look up message events > Find out what happened to your messages by recipient or event type, from the last 24 hours to the last 90 days. Source: https://www.coritan.com/docs/mail/smtp-relay/events/ In the dashboard: - /dashboard/mail/…/events: https://www.coritan.com/dashboard/mail The **Events** tab of an SMTP Relay service lists what happened to the messages it sent: each one we queued, delivered, deferred or bounced, each complaint, and each message or recipient the relay refused. Use it to answer a question such as "did my receipt reach alex@example.com?", up to 90 days back. To have events sent to your application as they happen, add a [webhook](/docs/mail/smtp-relay/webhooks/). ## Before you begin - An SMTP Relay service that has sent mail. Events appear a short time after each step of a delivery. ## Look up events 1. In the dashboard, go to [**Email**](https://www.coritan.com/dashboard/mail), open the SMTP Relay service, then the **Events** tab. 2. In **Event**, choose the kind of event to list, or leave **All events**. [Event categories](#event-categories) describes each one. 3. In **Window**, choose how far back to look: **Last 24 hours**, **Last 3 days**, **Last 7 days**, **Last 30 days** or **Last 90 days**. The tab opens on **Last 7 days**. 4. To see the events for one recipient, enter the whole address in **Recipient**, such as `alex@example.com`, and select **Filter**. Part of an address finds nothing. **Clear** removes the recipient filter. Select **Refresh** to load events that arrived since you opened the tab. ## Result The **Delivery events** card lists the matching events, newest first: **Recipient** : The recipient, with the sender under it. **Event** : The event's category. **Response** : What the receiving server replied. When there is no reply, the server we connected to, or the detailed event type. Hover over it to see the detailed event type. **When** : How long ago the event happened. Hover over it to see the date and time. The list holds up to 200 events. When it is full, a note under it says `Showing the latest 200. Narrow the window or filter by recipient to see older events.` The count above the list is the number of events shown, so it stops at 200 too. ## Event categories | `category` | What happened | | --- | --- | | `accepted` | We queued the message for delivery. | | `delivered` | The receiving server took the message. | | `deferred` | The receiving server refused the message for now, or a sending limit held it back. We try again later. | | `bounced` | The message was not delivered and we will not try again. We add the recipient to the [suppression list](/docs/mail/smtp-relay/suppressions/). | | `complaint` | A report about the message reached us: the recipient marked it as spam, or a mailbox provider reported it as fraud or as failing authentication. We add the recipient to the suppression list. | | `rejected` | The relay refused the message, or one of its recipients, when your application submitted it over SMTP. The reply your application got says why. | The **Event** column and the **Event** filter write each category with a capital letter. [Receive delivery events with webhooks](/docs/mail/smtp-relay/webhooks/#events) lists the detailed event types behind each category. Rows marked **Other** (`other` or `null` in the API) are steps of a delivery that have no category of their own, such as the end of a delivery attempt. The **Event** filter cannot select them. The **Event** filter also offers `suppressed` and `report`, and both normally find nothing: - We record no `suppressed` event. A message to a suppressed address appears as `rejected` when the relay refused the address over SMTP, and not at all when the send API left it out. - DMARC and TLS reports about your domains are kept apart from events. The dashboard does not list them, and the API returns them with the [reputation report](/docs/mail/smtp-relay/deliverability/#dmarc-and-tls-reports). ## Troubleshooting **No events in this window** : Nothing matches the filters. Choose a longer **Window**, check the whole address in **Recipient**, or select **Clear**. A message sent in the last minute or so may not have its events yet. A message you sent is not listed : When the relay refused the whole message before it was queued, your application got the refusal as an SMTP reply or an API error, and the list may hold a `rejected` event or nothing. [Send over SMTP with credentials](/docs/mail/smtp-relay/credentials/#troubleshooting) and [Send email over HTTPS](/docs/mail/smtp-relay/send-with-the-api/#troubleshooting) explain each refusal. A recipient has `accepted` but nothing after it : The message is still in the queue, or the receiving server has not answered yet. A `deferred` event means we are trying again. Check again later, and look at **Response** on the latest event. ## Related - [Receive delivery events with webhooks](/docs/mail/smtp-relay/webhooks/) - [Manage the suppression list](/docs/mail/smtp-relay/suppressions/) - [Events API reference](/docs/api/reference/client/mail/smtp-relay/#op-get-api-v1-client-smtp-relay-service-id-events) ## With the API ```bash curl "https://api.coritan.com/api/v1/client/smtp-relay/4812/events?category=bounced&hours=168&limit=50" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` `category` : Optional. One of `accepted`, `delivered`, `deferred`, `bounced`, `complaint`, `rejected` and `report`. Any other value answers an empty list. `recipient` : Optional. The whole recipient address. Case does not matter. `hours` : How far back to look, 1–2160 hours (90 days). The default is `24`. `limit` : The most events to return, 1–500. The default is `100`. A value for `hours` or `limit` outside its range answers `422`. The answer lists the newest events first: ```json { "items": [ { "id": 90415, "event_type": "delivery.failed", "category": "bounced", "queue_id": "7d2c91a04e", "message_id": "<175890432171.2481.9311874401294517206@example.com>", "sender": "receipts@example.com", "recipient": "alex@example.com", "remote_host": "mx.example.net", "response": "550 5.1.1 : Recipient address rejected: User unknown", "local_ip": "203.0.113.25", "node_id": 3, "occurred_at": "2026-09-16T10:52:08" } ], "total": 1 } ``` `id` : The event's id, the same one a webhook request carries. `event_type` : The detailed event type, such as `delivery.failed`. `category` : The category from [Event categories](#event-categories), or `other` or `null` for a step that has none. `queue_id` : Our id for the message in the delivery queue, the same on every event about that message. `message_id` : The message's `Message-ID` header. `sender` and `recipient` : The sender and recipient addresses, in lower case. An event about the whole message, such as `accepted`, names only its first recipient. `remote_host` : The receiving server we connected to. `response` : The receiving server's reply or the reason for the event, up to 2,000 characters. `local_ip` : The address the message left our servers from. `node_id` : Our id for the mail server that handled the event. Use it only to tell servers apart. `occurred_at` : When the event happened, in UTC, with no time zone suffix. `total` : The number of events in this answer, which is never more than `limit`. It does not count the events the limit left out. Any field except `id`, `event_type` and `occurred_at` is `null` when the event does not carry it. ### On a Mail Hosting service Mail Hosting has no **Events** tab, but the same operation works under `/client/mail/{service_id}/events` and lists the events we record for mail the service's mailboxes send. The **Recent activity** card on its **Overview** tab shows the latest eight events from the last seven days. Some bounces reach the sending mailbox only as an `Undelivered Mail Returned to Sender` message and are not listed. ## API - `GET /api/v1/client/smtp-relay/{service_id}/events`: List events (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay/#op-get-api-v1-client-smtp-relay-service-id-events) - `GET /api/v1/client/mail/{service_id}/events`: List events (https://www.coritan.com/docs/api/reference/client/mail/mail/#op-get-api-v1-client-mail-service-id-events) # How SMTP Relay sending limits work > How the monthly allowance, its buffer and the hourly limit work, and how the hourly limit rises as your relay sends clean mail. Source: https://www.coritan.com/docs/mail/smtp-relay/sending-limits/ In the dashboard: - /dashboard/mail/…/overview: https://www.coritan.com/dashboard/mail An SMTP Relay service has two sending limits. The *monthly allowance* is how many emails the relay may send in a calendar month, and the plan sets it. The *hourly limit* is how many emails the relay sends in an hour. It starts small on every new relay and rises as the relay builds a clean sending history. Both are on the service's **Overview** tab in the dashboard. ## The monthly allowance The free plan includes 3,000 emails a month and stops there. Each paid plan includes more and adds a 12% *buffer*: the relay can send 12% more than the plan's number before sending stops. On a plan of 25,000 emails a month, the allowance is 28,000. The [Mail page](https://www.coritan.com/solutions/mail) lists the plans and their prices. We count like this: - Each message we accept counts once, however many recipients it has. The send API sends a marketing message as one copy for each recipient, so each copy counts. - A message counts when we accept it, whether it is later delivered, deferred or bounced. A message we refuse does not count. - The count starts again at 00:00 UTC on the 1st of each month. - We add up accepted messages once an hour, so the count trails your sending by up to about an hour. The first figure on the **Overview** tab is **Emails this month**, or **Emails this month (with buffer)** on a paid plan. It shows the count against the allowance and when it resets. A relay that our staff set up with no monthly cap shows **No monthly cap**. ## What happens near the allowance As the count rises, we email you at your account's address, and the **Overview** tab shows an alert: | When the count reaches | Email subject | Alert on the Overview tab | | --- | --- | --- | | 80% of the plan's number | `SMTP relay #4812: 80% of this month's emails used` | **Monthly allowance almost used** | | 90% of the plan's number | `SMTP relay #4812: 90% of this month's emails used` | **Monthly allowance almost used** | | The plan's number, on a paid plan | `SMTP relay #4812: monthly allowance reached` | **Monthly allowance reached; sending from the buffer** | | The plan's number, on the free plan | `SMTP relay #4812: monthly allowance reached` | **Sending has stopped for this month** | | The allowance, buffer included | `SMTP relay #4812: sending has stopped for this month` | **Sending has stopped for this month** | The subject starts with the relay's service ID. We send each email once a month. When the count passes several points between two counts, we send only the email for the highest. While the relay sends from its buffer, the alert says how many emails the buffer still covers. Once the count reaches the allowance, we refuse new mail: - Over SMTP, the relay answers `550 5.7.1 Monthly allowance and its 12% buffer are used up; upgrade the plan to continue`. On the free plan it answers `550 5.7.1 Monthly sending allowance reached; upgrade the plan to continue`. - The send API answers `429` with the same text in `detail`. Both check the count plus the recipients of the message in hand. Near the end of the allowance, a message to many recipients can be refused while a message to one recipient still goes out. Because the count trails by up to about an hour, a relay can go past its allowance by up to about an hour of sending before we refuse mail. Sending starts again at 00:00 UTC on the 1st. Each alert has **Change plan**, which opens the service's **Billing** tab. The plan-change dialog there only moves a service between billing cycles of the same plan, and each relay plan is a plan of its own, so it cannot move the relay to a larger plan. To raise the allowance before the month ends, [contact support](/docs/support/conversations/). ## The hourly limit The hourly limit caps how many emails the relay sends in an hour, which protects the reputation of the addresses it shares with other senders. A new relay starts with a small limit, and a daily review raises it one step at a time. The steps are 100, 250, 500, 1,000, 2,500, 5,000, 10,000, 25,000 and 50,000 emails an hour. Mail over the limit waits in the queue and goes out in a later hour. While it waits, the **Events** tab can show `deferred` events for it. If mail keeps arriving faster than the limit, the queue grows, and once six hours of mail at the hourly limit is waiting, we refuse new messages until the queue shrinks. ### How the limit rises The review runs once a day and raises the limit one step when all of these are true: - The relay has sent clean mail for its first 14 days, or has sent 5,000 clean messages sooner. Clean means the next rule holds. - The relay is not under review, and its sending is not paused. - Over the last 14 days, fewer than 2% of the messages it sent bounced and fewer than 0.1% drew a complaint. - The relay has had its current limit for at least 2 days. - In the last 14 days it sent at least 12 times its hourly limit, such as 1,200 messages at 100 an hour. - The next step is not above the plan's automatic ceiling. A raised limit takes effect within about an hour. The *automatic ceiling* is the highest step at or below the plan's monthly emails divided by 25: a rate at which the relay could send the plan's monthly emails in about a day. A relay with no monthly cap can climb every step. | Emails a month | Automatic ceiling | | --- | --- | | 3,000 | 100 an hour | | 25,000 | 1,000 an hour | | 50,000 | 1,000 an hour | | 100,000 | 2,500 an hour | | 250,000 | 10,000 an hour | | 500,000 | 10,000 an hour | | 1,000,000 | 25,000 an hour | On the free plan, the limit does not rise above 100 on its own. Once the limit reaches the ceiling, the **Hourly limit** card says **This plan's automatic ceiling is reached; ask for more below**. To go higher, [request a higher limit](/docs/mail/smtp-relay/request-a-higher-limit/): our staff can set any limit, above the top step too. Changing the plan does not change the hourly limit, only the ceiling the review can reach. The limit can also go down. When a relay goes under review or has its sending paused, or when 2% of its messages in the last 14 days bounce or 0.1% draw a complaint, the next daily review can set its limit back to the starting limit. ### The Hourly limit card On the **Overview** tab, the **Hourly limit** card shows: **Current limit** : The limit now, such as `250 emails an hour`. **Next step** : The step the review raises the limit to next. The card leaves it out once the next step is above the plan's ceiling. **Set** : How and when the current limit was set: **Starting limit**, **Raised automatically**, **Set by support** or **Reset after a reputation review**, then how long ago. **Request limit increase…** asks our staff for more. See [Request a higher hourly limit](/docs/mail/smtp-relay/request-a-higher-limit/). ## Dedicated sending addresses A relay sends from addresses it shares with other relays unless it has a *dedicated address*: an IPv4 address that only your relay sends from, with a reverse DNS name of its own. Its reputation depends on your mail alone. - Plans of 250,000 emails a month and more include one dedicated address. - On plans of 100,000 emails a month and more, you can add dedicated addresses for $50 a month each. Each one you add is a service of its own, with its own invoice ([Dedicated IP add-ons](/docs/mail/billing/#dedicated-ip-add-ons)). You add one with **Order dedicated IP** on the **Sending IPs** card of the **Overview** tab, which opens the address's order page ([Order a dedicated sending IP](/docs/mail/order-a-mail-service/#order-a-dedicated-sending-ip)). On a smaller plan the button is greyed out, and the card says which plan you need. Until we have assigned an address, the card says **We are assigning your dedicated addresses**. Then it lists each address with its reverse DNS name and its current hourly cap, such as `500/h`. A **Warming up** badge shows until the address reaches its last stage ([Warm-up](#warm-up)). An address you bought on its own has an **Add-on** badge, and one that is turned off has a **Disabled** badge. From then on, the relay's mail leaves from its dedicated addresses. When you [cancel](/docs/billing/cancel-a-service/) a dedicated address you bought on its own and it ends, we release the address. When a relay's last dedicated address goes, its mail goes back to the shared addresses. Cancelling the relay does not cancel the addresses you bought on their own, so cancel those too. ### Warm-up Mail providers distrust a new address that suddenly sends a lot. So each dedicated address starts with a small hourly cap, which rises in stages: | Stage | Hourly cap | Emails to send in the stage | | --- | --- | --- | | 0 | 50 | 200 | | 1 | 200 | 800 | | 2 | 500 | 2,000 | | 3 | 1,000 | 4,000 | | 4 | 2,000 | 8,000 | | 5 | 5,000 | 20,000 | | 6 | 10,000 | 40,000 | | 7 | 20,000 | 80,000 | | 8 | None of its own | — | We check once a day. An address moves up one stage when it has spent at least 2 days in its stage, sent the emails the table gives, and kept bounces under 2% and complaints under 0.1% in that stage. We can also hold every address in a region at its stage for a while, when large providers are filtering mail from that region. From stage 8, only the relay's hourly limit applies. During warm-up, mail to Gmail, Microsoft (Outlook.com, Hotmail, Live and MSN), Yahoo (with AOL) and Apple (iCloud) has a cap of its own at each address: a quarter of the address's hourly cap, and at least 10. Mail over a cap waits in the queue, as mail over the hourly limit does. A warming address can have a lower cap than the relay's hourly limit, so plan large sends for after the warm-up. ## With the API [`GET /client/smtp-relay/{service_id}/usage`](/docs/api/reference/client/mail/smtp-relay/#op-get-api-v1-client-smtp-relay-service-id-usage) returns the month's count, a daily series and the relay's limits: ```bash curl "https://api.coritan.com/api/v1/client/smtp-relay/4812/usage?days=3" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` `days` is how far back the series goes, 1–365 days, and 30 when you leave it out. A value outside that range answers `422`. ```json { "series": [ {"day": "2026-09-14", "sent": 1204, "delivered": 1187, "bounced": 6, "deferred": 23, "complaints": 0}, {"day": "2026-09-15", "sent": 1318, "delivered": 1296, "bounced": 9, "deferred": 31, "complaints": 1}, {"day": "2026-09-16", "sent": 612, "delivered": 598, "bounced": 3, "deferred": 12, "complaints": 0} ], "month_to_date_sent": 21460, "quota_messages_month": 25000, "quota_buffer_pct": 12, "monthly_allowance": 28000, "limits": { "monthly_messages": 25000, "quota_buffer_pct": 12, "monthly_allowance": 28000, "month_to_date_sent": 21460, "remaining": 6540, "over_quota": false, "sending_stopped": false, "free_tier": false, "domains": null, "credentials": null, "rate_per_hour": 250, "hourly_limit_since": "2026-09-11T04:15:09.482113+00:00", "hourly_limit_source": "auto", "hourly_limit_next": 500, "hourly_limit_auto_ceiling": 1000, "dedicated_ip_count": 0, "dedicated_ip_addon_eligible": false, "dedicated_ip_min_monthly": 100000 } } ``` `series` : One entry for each day with mail, oldest first, from `days` days ago up to today (UTC). Each has the `day` and its counts: `sent` (messages accepted), `delivered`, `bounced`, `deferred` and `complaints`. Days with no mail are left out. `month_to_date_sent` : Messages accepted since the 1st, as of the last hourly count. `quota_messages_month`, `quota_buffer_pct`, `monthly_allowance` : The plan's emails a month, the buffer as a percentage, and the allowance they make together. A `quota_messages_month` of `0` means no monthly cap. `limits` : The relay's limits. The service summary, [`GET /client/smtp-relay/{service_id}`](/docs/api/reference/client/mail/smtp-relay/), returns the same object. The fields in `limits`: `monthly_messages`, `quota_buffer_pct`, `monthly_allowance`, `month_to_date_sent` : The same figures as at the top level. `remaining` : What is left of the allowance, never below `0`. `null` with no monthly cap. `over_quota` : `true` once the count reaches the plan's number, so the relay is sending from its buffer or has stopped. `sending_stopped` : `true` once the count reaches the allowance. `free_tier` : `true` on the free plan. `domains`, `credentials` : How many sending domains and SMTP credentials the plan allows. `null` means no cap. `rate_per_hour` : The hourly limit. `hourly_limit_since` : When the hourly limit was set. `hourly_limit_source` : How it was set: `start`, `auto` (the daily review), `staff` or `reset`. `hourly_limit_next` : The next step, or `null` above the top step. The review raises the limit to it only when it is not above `hourly_limit_auto_ceiling`. `hourly_limit_auto_ceiling` : The highest limit the review reaches on this plan. `dedicated_ip_count` : How many dedicated addresses the relay has, from the plan and bought on their own. `dedicated_ip_addon_eligible` : `true` when the plan allows buying more dedicated addresses. `dedicated_ip_min_monthly` : The emails a month a plan needs for that: `100000`. The same operation is under [`GET /client/mail/{service_id}/usage`](/docs/api/reference/client/mail/mail/#op-get-api-v1-client-mail-service-id-usage), which answers for both products. On a Mail Hosting service it returns the `series` and `month_to_date_sent` for the mail its mailboxes send. Mail Hosting has no monthly allowance, so `quota_messages_month`, `quota_buffer_pct` and `monthly_allowance` are `0`, and the answer has no `limits`. ## API - `GET /api/v1/client/smtp-relay/{service_id}/usage`: Usage report (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay/#op-get-api-v1-client-smtp-relay-service-id-usage) - `GET /api/v1/client/mail/{service_id}/usage`: Usage report (https://www.coritan.com/docs/api/reference/client/mail/mail/#op-get-api-v1-client-mail-service-id-usage) # Request a higher hourly limit > Ask for a higher hourly sending limit on an SMTP Relay service from its Overview tab, and follow the request in support. Source: https://www.coritan.com/docs/mail/smtp-relay/request-a-higher-limit/ In the dashboard: - /dashboard/mail/…/overview: https://www.coritan.com/dashboard/mail Every SMTP Relay service has an hourly limit that rises by itself as the relay sends clean mail, up to a ceiling its plan sets ([How the limit rises](/docs/mail/smtp-relay/sending-limits/#how-the-limit-rises)). When you need more before the daily review gets there, or more than the ceiling, ask for it. The request opens a support conversation, and our staff set the new limit when they grant it. ## Before you begin - You need an SMTP Relay service. Mail Hosting has no request form. - Ask for a limit higher than the relay's current one, and at most 1,000,000 emails an hour. - You can send one request a day for each relay. A second request on the same day (UTC) opens no new conversation. - Have the numbers ready: what the relay sends, and the rate you expect at the busiest time. ## Send the request 1. In the dashboard, go to **Email** and open the relay. 2. On the **Overview** tab, find the **Hourly limit** card and select **Request limit increase…**. 3. In **Emails per hour**, enter the limit you need. The field starts at the next step up from the current limit. 4. In **What the relay sends and why the volume is going up**, describe the mail and the reason for more, such as a launch or a seasonal peak, with the rate you expect at peak. 5. Select **Send request**. ## Result A toast says `Requested; limit increases are reviewed within a few business hours`. Under **Support**, a new conversation called `Hourly sending limit increase for SMTP relay #4812` holds the number you asked for and your reason. It also holds the figures our staff review: the plan's monthly emails, how many emails the relay has sent this month, and its recent sent count, bounce rate and complaint rate. We reply in that conversation. When we grant the request, the **Hourly limit** card shows the new **Current limit**, and **Set** reads **Set by support**. The new limit reaches the mail servers within the hour. From there, the daily review can still raise the limit one step at a time, up to the plan's ceiling. A limit our staff set can be reset like any other when the relay's rates rise ([How the limit rises](/docs/mail/smtp-relay/sending-limits/#how-the-limit-rises)). ## Troubleshooting **Send request** stays off : The number in **Emails per hour** is not higher than the current limit, which the dialog states. `The relay may already send 250 an hour` : The number is not higher than the current limit. Enter a larger one. **Request limit increase…** is off : The relay is one of the platform's internal tenants, whose limits only our staff set. See [Internal mail tenants](/docs/mail/internal-tenants/). No new conversation appears : The relay already has a request today. The first conversation keeps its number and reason, so reply in it to change them. **Could not send the request** : The request failed, and the message after the title says why. ## Related - [How SMTP Relay sending limits work](/docs/mail/smtp-relay/sending-limits/) - [Sending reputation and deliverability](/docs/mail/smtp-relay/deliverability/) - [Support conversations](/docs/support/conversations/) - [`POST /client/smtp-relay/{service_id}/limits/increase-request`](/docs/api/reference/client/mail/smtp-relay/#op-post-api-v1-client-smtp-relay-service-id-limits-increase-request) in the API reference ## With the API ```bash curl -X POST https://api.coritan.com/api/v1/client/smtp-relay/4812/limits/increase-request \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"requested_per_hour": 1000, "reason": "Order confirmations for a shop launching on 1 October, about 800 an hour at peak"}' ``` `requested_per_hour` : Required. The limit you ask for, 10–1,000,000 emails an hour, and higher than the current limit. `reason` : Optional, up to 2,000 characters. What the relay sends and why the volume is going up. The answer is `201`: ```json { "ok": true, "conversation_id": 7731, "current_per_hour": 250, "requested_per_hour": 1000, "message": "Requested; limit increases are reviewed within a few business hours" } ``` `conversation_id` is the support conversation. Follow it with [`GET /chat/conversations/{conversation_id}`](/docs/api/reference/client/support/#op-get-api-v1-chat-conversations-conversation-id), and reply with [`POST /chat/conversations/{conversation_id}/messages`](/docs/api/reference/client/support/#op-post-api-v1-chat-conversations-conversation-id-messages). A second request on the same day (UTC) answers with the first request's `conversation_id`, and that conversation keeps its first number and reason. To see the new limit once we grant it, read `limits.rate_per_hour` from the [usage report](/docs/mail/smtp-relay/sending-limits/#with-the-api). The same operation is under `/client/mail/{service_id}/limits/increase-request`, which answers the same way for a relay. | Status | `detail` | Cause | | --- | --- | --- | | `422` | `The relay may already send 250 an hour` | `requested_per_hour` is not higher than the current limit. | | `422` | A validation error | `requested_per_hour` is missing or outside 10–1,000,000, or `reason` is longer than 2,000 characters. | | `404` | `Not an SMTP Relay service` | The service is Mail Hosting. | | `404` | `Mail service not found` | No mail service with that ID belongs to your account. | | `409` | `This mail service is still provisioning` | We are still setting up the relay. | ## API - `POST /api/v1/client/smtp-relay/{service_id}/limits/increase-request`: Request limit increase (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay/#op-post-api-v1-client-smtp-relay-service-id-limits-increase-request) - `POST /api/v1/client/mail/{service_id}/limits/increase-request`: Request limit increase (https://www.coritan.com/docs/api/reference/client/mail/mail/#op-post-api-v1-client-mail-service-id-limits-increase-request) # Sending reputation and deliverability > How we measure your relay's bounce and complaint rates, when sending pauses, and how to read the Deliverability card. Source: https://www.coritan.com/docs/mail/smtp-relay/deliverability/ In the dashboard: - /dashboard/mail/…/overview: https://www.coritan.com/dashboard/mail Mail providers decide whether your mail reaches the inbox largely from two numbers: how much of it bounces, and how often recipients report it as spam. We measure both for every SMTP Relay and Mail Hosting service. A service whose rates get too high has its sending paused. On an SMTP Relay service, the **Deliverability** card on the **Overview** tab shows the rates, where sending pauses and a health check of your setup. ## How we measure - The *bounce rate* is the share of sent messages that ended as `bounced` [events](/docs/mail/smtp-relay/events/#event-categories): not delivered, and not tried again. - The *complaint rate* is the share that drew a `complaint` event: the recipient marked the message as spam, or a mailbox provider reported it as fraud or as failing authentication. - Both rates use the messages we accepted from the service over the last seven days. Every credential, API key and domain on the service counts together. We work out the rates of every active service every 30 minutes. The counts behind them are added up once an hour, so they trail your sending by up to about an hour. When the seven days hold too few messages, we do not judge the rates at all. ## When sending pauses | Rate | Sending paused from | | --- | --- | | Bounce rate | 10% | | Complaint rate | 0.5% | Reaching a line is enough: a bounce rate of exactly 10% pauses sending. The **Deliverability** card shows the line under each rate, such as `Pauses at 10%`. ### Under review Our staff may review a service whose rates rise before they reach a pause line. Sending carries on. Above the tabs, every page of the service shows the **Under review** alert with the figures behind it, such as `flagged: 6.12% bounces, 0.04% complaints over 7d`. While the review is open, a relay's hourly limit does not rise, and the next daily check can set it back to the starting limit. The alert stays until our staff close the review, even after the rates fall. Once the rates are back to normal, the alert keeps its title and says `We are reviewing your delivery rates. Sending continues while the review is open.` ### Sending paused When either rate reaches its pause line, we pause the service's sending: - On an SMTP Relay service, no SMTP credential can sign in, and the send API answers `400` with `Sending is paused on this service; contact support`. A message that still reaches the relay over SMTP is refused with `550 5.7.1 Sending is paused for this account; contact support`. - On a Mail Hosting service, its mailboxes cannot sign in, send or receive mail. - Above the tabs, the **Sending is paused** alert shows the figures, such as `auto-frozen: 11.4% bounces, 0.08% complaints over 7d`. The **Status** on the **Service** card reads **Sending paused**, and on a relay the **Send** tab shows the alert too. - On a relay, the next daily check can set the hourly limit back to the starting limit. We do not email you when a service goes under review or is paused. The alerts on the service's pages are where you see it. Only our staff lift a pause. Fix the cause first ([Bring the rates down](#bring-the-rates-down)), then [contact support](/docs/support/conversations/) and say what you changed. The check still looks at the last seven days: if the rates in that window are still at a pause line when our staff lift the pause, the next check pauses sending again. Our staff can also pause a service by hand. The alert has the same title, and without figures it says `Bounce or complaint rates crossed the platform threshold. Clean your lists, then contact support to resume.` Separately from these rates, we turn off a single mailbox that suddenly sends far more than it usually does. See [Manage mailboxes](/docs/mail/mail-hosting/mailboxes/#troubleshooting). ## Bring the rates down - Send only to people who asked for your mail. Bought lists and old lists bounce and draw complaints. - Confirm new addresses when people sign up, such as with a confirmation email, so typing mistakes never reach your list. - Send newsletters and campaigns as `marketing`. Recipients then get an unsubscribe button in their mail app and can use it instead of reporting spam. See [Transactional and marketing mail](/docs/mail/smtp-relay/message-categories/). - Leave addresses on the [suppression list](/docs/mail/smtp-relay/suppressions/) unless you know the problem is fixed. Each one bounced or complained before. - Follow `bounced` and `complaint` events on the **Events** tab or with [webhooks](/docs/mail/smtp-relay/webhooks/), and take those addresses off your own lists too. ## Where your mail leaves from We manage the shared addresses your mail leaves from and watch their reputation, so you have nothing to set up for them. If you want addresses that only your relay sends from, a [dedicated address](/docs/mail/smtp-relay/sending-limits/#dedicated-sending-addresses) is an add-on. ## The Deliverability card On an SMTP Relay service, go to **Email**, open the relay, then the **Overview** tab. Mail Hosting services have no card, and their figures are in the [API](#with-the-api). The card covers the last seven days, as its description says. It starts with a verdict on the rates as they are now: | `verdict` | What the card says | | --- | --- | | `ok` | `Your bounce and complaint rates are healthy.` | | `review` | `Your bounce or complaint rate is high. Clean your lists before sending pauses.` | | `freeze` | `Sending is paused because your bounce or complaint rate got too high. Clean your lists, then contact support.` | | `insufficient` | `Too few messages in the last 7 days to judge.` | When the service is under review or paused, the figures behind it follow the verdict in brackets. The verdict follows the current rates, while the alerts above the tabs stay until our staff close them. So the card can call the rates healthy while the **Under review** alert is still there. Four figures follow: **Inbox health** : A score out of 100, with a grade. See [The inbox health score](#the-inbox-health-score). **Sent** : Messages accepted in the window. **Bounce rate** : The bounce rate, with the pause line under it, such as `Pauses at 10%`. The rate turns to the warning colour at the pause line. **Complaints** : The complaint rate, with its pause line. Then comes a list of checks, each with a short detail, such as **Domains** · `1 verified`. At the bottom, **Default message category** sets the category of messages that do not choose one ([Transactional and marketing mail](/docs/mail/smtp-relay/message-categories/#set-the-default-category)). When the report cannot load, the card says `Reputation data is not available right now.` ### The checks Every check is about your own setup and sending. | Check | Weight | Passes when | Warns or waits when | Fails when | | --- | --- | --- | --- | --- | | **Domains** | 25 | Every domain is verified. | Some domains are still pending. | No domain is added, or none is verified. | | **SPF and DKIM** | 20 | Both records pass on every verified domain. | No domain is verified yet. | A verified domain is missing either record. | | **Bounces and complaints** | 15 | Bounces are under 2% and complaints under 0.1%. | Bounces reach 2% or complaints 0.1%, or the window holds fewer than 50 messages. | Bounces reach 5% or complaints 0.3%. | | **DMARC** | 10 | Every verified domain has its DMARC record, and at least one is at `p=quarantine` or `p=reject`. | Every verified domain is at `p=none`, or no domain is verified yet. | A verified domain's DMARC record is missing or out of date. | **Bounces and complaints** uses stricter lines than the pause lines: 2% bounces and 0.1% complaints are what Gmail asks of senders. Three more checks carry no weight: **TLS** : Shown when we have recent results, and says whether your mail reached the large providers over an encrypted connection. **Sending** : Shown only while sending is paused, as `sending is paused`. **Review** : Shown only while the service is under review, as `our reputation checks flagged this for review`. ### The inbox health score Each check with a weight earns all of it when it passes, half when it warns or waits for data, and none when it fails. **Inbox health** is the weight earned out of the weight of the checks shown, as a number out of 100. The score counts those checks alone. While sending is paused, or while **SPF and DKIM** fails, the score is at most 40. | Score | Grade | | --- | --- | | 90–100 | Excellent | | 75–89 | Good | | 50–74 | Fair | | 0–49 | Poor | ## DMARC and TLS reports Mail providers send *aggregate reports* about the mail they receive from your domains: how many messages, from which servers, and whether they passed SPF, DKIM and DMARC. They send them to the `rua=` address in your DMARC record, when the record the dashboard shows has one. The reports drive the [DMARC stage ramp](/docs/mail/dmarc/#when-the-stage-moves-up). On a Mail Hosting domain, *TLS reports* describe the mail sent to you: whether other servers could reach yours over an encrypted connection. They go to the address in the `_smtp._tls` record. [Mail DNS records](/docs/mail/dns-records/) lists both records. The dashboard does not list the reports. The API returns the latest ones on your own domains with the reputation report below. ## With the API [`GET /client/smtp-relay/{service_id}/reputation`](/docs/api/reference/client/mail/smtp-relay/#op-get-api-v1-client-smtp-relay-service-id-reputation) returns everything the card shows, and the reports: ```bash curl "https://api.coritan.com/api/v1/client/smtp-relay/4812/reputation?limit=5" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` `limit` is how many reports to return, newest first: 1–200, and 50 when you leave it out. A value outside that range answers `422`. ```json { "health": { "score": 93, "grade": "excellent", "items": [ {"key": "domains", "status": "pass", "weight": 25, "detail": "1 verified"}, {"key": "spf_dkim", "status": "pass", "weight": 20, "detail": "SPF and DKIM published on every verified domain"}, {"key": "dmarc", "status": "warn", "weight": 10, "detail": "p=none: monitoring only, tightened automatically after two clean weeks of reports"}, {"key": "rates", "status": "pass", "weight": 15, "detail": "bounce 1.2%, complaints 0.048%"} ] }, "default_category": "transactional", "window_days": 7, "sent": 8412, "bounced": 101, "complaints": 4, "bounce_pct": 1.2, "complaint_pct": 0.048, "verdict": "ok", "thresholds": { "bounce_freeze_pct": 10.0, "complaint_freeze_pct": 0.5 }, "sending_frozen": false, "review_flag": false, "note": null, "domains": [ { "domain": "example.com", "status": "verified", "checks": {"ownership": true, "mx": false, "spf": true, "dkim": true, "dmarc": true}, "dmarc_policy": "none", "mta_sts_mode": "testing" } ], "reports": [ { "id": 90211, "domain": "example.com", "type": "dmarc", "reporter": "google.com", "range_begin": "2026-09-15T00:00:00", "range_end": "2026-09-15T23:59:59", "total": 1180, "passed": 1176, "failed": 4, "summary": null, "received_at": "2026-09-16T06:12:44" } ] } ``` `health` : The inbox health score: `score` (0–100), `grade` (`excellent`, `good`, `fair` or `poor`) and `items`, one for each check. Each item has a `key`, a `status` (`pass`, `warn`, `fail` or `info`), its `weight` in the score and a `detail`. The score counts these items alone. `health.items[].key` : `domains`, `spf_dkim`, `dmarc`, `rates` (**Bounces and complaints**), `tls`, `frozen` (**Sending**) and `review`. A Mail Hosting service also gets `mx`, with a weight of 5: it passes when every verified domain receives its mail with us, and warns otherwise. `default_category` : `transactional` or `marketing`. `window_days` : The length of the window, in days. `sent`, `bounced`, `complaints` : Messages accepted, bounces and complaints in the window. `bounce_pct`, `complaint_pct` : The rates in percent: `bounce_pct` to two decimal places, `complaint_pct` to three. Both are `0.0` when nothing was sent. `verdict` : `ok`, `review` or `freeze`, or `insufficient` when the window holds too few messages to judge. `thresholds` : The pause lines in percent: `bounce_freeze_pct` and `complaint_freeze_pct`. `sending_frozen`, `review_flag` : Whether sending is paused, and whether the service is under review. `note` : The figures behind the review or the pause, such as `flagged: 6.12% bounces, 0.04% complaints over 7d`, or `null`. `domains` : Each domain with its `status`, the results of its `checks`, its DMARC stage in `dmarc_policy`, and `mta_sts_mode`, the mode of the MTA-STS policy we serve for it. [Mail DNS records](/docs/mail/dns-records/#what-each-check-looks-for) explains the checks. `reports` : The latest DMARC and TLS reports on your own domains, newest first, up to `limit`. Each report has: `type` : `dmarc` for a DMARC aggregate report, or `tls` for a TLS report. `domain`, `reporter` : The domain the report is about, and the organisation that sent it. `range_begin`, `range_end` : The period the report covers, or `null` when it does not say. `total`, `passed`, `failed` : The messages or connections the report covers, and how many passed and failed. `summary` : Further detail we read from the report, or `null`. `received_at` : When the report reached us. The service summary, [`GET /client/smtp-relay/{service_id}`](/docs/api/reference/client/mail/smtp-relay/), carries the results of the last check: `bounce_rate_pct` and `complaint_rate_pct` to two decimal places, `reputation_checked_at`, `reputation_note`, `sending_frozen` and `review_flag`. The same report is under [`GET /client/mail/{service_id}/reputation`](/docs/api/reference/client/mail/mail/#op-get-api-v1-client-mail-service-id-reputation), which answers for both products. On a Mail Hosting service, its `reports` can also hold `tls` reports. ## API - `GET /api/v1/client/smtp-relay/{service_id}/reputation`: Reputation report (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay/#op-get-api-v1-client-smtp-relay-service-id-reputation) - `GET /api/v1/client/mail/{service_id}/reputation`: Reputation report (https://www.coritan.com/docs/api/reference/client/mail/mail/#op-get-api-v1-client-mail-service-id-reputation) # How webmail works > What Coritan Webmail is, how to open it, which mailboxes it shows, and what each way of signing in lets you do. Source: https://www.coritan.com/docs/mail/webmail/ In the dashboard: - /webmail: https://www.coritan.com/webmail Coritan Webmail reads and sends mail in your browser, for mailboxes that Coritan hosts. It is at [www.coritan.com/webmail](https://www.coritan.com/webmail). The addresses mail.coritan.com and webmail.coritan.com send you there too, to the same page the link named. Webmail is for anyone with a mailbox here: your own coritan.gg address, a mailbox on a [Mail Hosting](/docs/mail/mail-hosting/) service, or a mailbox someone shared with you. It changes the mailbox on the mail server itself, so mail you read, sort or send there shows the same way in your mail apps. ## Open webmail - In the dashboard, select **Webmail** in the top bar. The account menu behind your initials has **Webmail** too. - In the dashboard's search palette (Ctrl K), select **Open webmail**. - On coritan.com, select **Webmail** at the top of the page while you are signed in. - On a Mail Hosting service, select **Open webmail** at the top of its panel. On its **Mailboxes** tab, **Open webmail** in a mailbox's menu opens webmail with that address filled in. Both open a new tab. When you are signed in to Coritan, your mailboxes open straight away. Otherwise webmail shows its sign-in card. ## Two ways to sign in *Your Coritan account* : While you are signed in on coritan.com, webmail opens every mailbox your account can open. It does not ask for a mailbox's password. *A mailbox's address and password* : You sign in to one mailbox, with no Coritan account needed. One browser keeps up to five mailboxes signed in this way. Your own coritan.gg address with your Coritan password is the exception: it opens every mailbox your account can open. [Sign in to webmail](/docs/mail/webmail/sign-in/) covers each step, the codes webmail can ask for, and each way to sign out. ## Mailboxes webmail opens *Your coritan.gg mailbox* : A Coritan account can have one address on `coritan.gg`, such as `alex@coritan.gg`. You take it when you sign up, or claim it later in webmail. See [Get a coritan.gg mailbox](/docs/mail/webmail/claim-a-mailbox/). *Mail Hosting mailboxes* : Mailboxes on your own domain, such as `alex@example.com`. Sign in to one with its address and password, or add it to your account so that it opens wherever you sign in to Coritan. *Mailboxes shared with you* : A mailbox that another Coritan account shares with you opens beside your own, once you accept the invitation. The account menu, behind the initials at the top right, lists every mailbox this browser can open in one list, however each one got there. [Open and share mailboxes in webmail](/docs/mail/webmail/mailboxes-and-sharing/) explains the list. A mailbox from an organization's storefront opens in that organization's own webmail. Webmail cannot open a mailbox at another provider, but it can [import its mail](/docs/mail/webmail/import/). ## What each way of signing in can do Reading, writing and every mailbox setting work the same whichever way you signed in. A few things depend on how you got in: - Sharing a mailbox, answering an invitation and adding a mailbox to your account need your Coritan account. Being signed in on coritan.com counts, and so does signing in to webmail with your coritan.gg address and your Coritan password. - Claiming a coritan.gg address needs you signed in on coritan.com. - The **Sessions** list, which shows the browsers signed in to a mailbox, is there for a mailbox you signed in to with its address and password. What you can change inside a mailbox depends on your role in it: its owner, someone it is shared with to read and send, or someone who can only read it. [Who sees each tab](/docs/mail/webmail/settings/#who-sees-each-tab) lists the settings each role reaches. ## What you can do in webmail Get in: - [Get a coritan.gg mailbox](/docs/mail/webmail/claim-a-mailbox/) - [Sign in to webmail](/docs/mail/webmail/sign-in/) - [Open and share mailboxes in webmail](/docs/mail/webmail/mailboxes-and-sharing/) Work with mail: - [Read and search mail in webmail](/docs/mail/webmail/read-and-search/) - [Write and send email in webmail](/docs/mail/webmail/write-and-send/) - [Keep an address book in webmail](/docs/mail/webmail/contacts/) - [Webmail keyboard shortcuts](/docs/mail/webmail/keyboard-shortcuts/) Set up the mailbox: - [Sort mail with labels and filters](/docs/mail/webmail/labels-and-filters/) - [Forward your mail to another address](/docs/mail/webmail/forwarding/) - [Send an automatic reply while you are away](/docs/mail/webmail/out-of-office/) - [Protect your webmail mailbox](/docs/mail/webmail/security/) - [Set up mail apps and devices](/docs/mail/webmail/mail-apps/) - [Import mail into webmail](/docs/mail/webmail/import/) - [Change webmail settings](/docs/mail/webmail/settings/) ## Around the screen The top bar holds the search, the status of new mail, the keyboard shortcuts button, **Settings**, the apps button and the account menu. The folders, your labels, the storage the mailbox uses, **Contacts** and **Settings** are down the left, under **Compose**. New mail and changes made elsewhere appear on their own. While the status reads **Live**, they appear the moment they happen. When it reads **Every 30 s**, the live connection is down and webmail checks for new mail every 30 seconds. The apps button, a grid beside your initials, links to the dashboard, its main sections and coritan.com. ## Cost Webmail has no charge of its own. A Mail Hosting mailbox is part of its service's plan ([How Mail Hosting and SMTP Relay are billed](/docs/mail/billing/)), and claiming your coritan.gg mailbox takes no order. # Sign in to webmail > Open webmail with your Coritan account or a mailbox's address and password, enter a code when asked, keep several mailboxes open, and sign out. Source: https://www.coritan.com/docs/mail/webmail/sign-in/ In the dashboard: - /webmail: https://www.coritan.com/webmail Webmail at [www.coritan.com/webmail](https://www.coritan.com/webmail) opens a mailbox in two ways: through your Coritan account, or with the mailbox's own address and password. This page covers both, the codes webmail can ask for, keeping several mailboxes open on one browser, and signing out. ## Before you begin - To use your Coritan account, have its email and password, and your authenticator app if the account has two-factor authentication on. - To use a mailbox's own sign-in, have its full address, such as `alex@example.com`, and its password: - for your coritan.gg mailbox, that is your Coritan password, unless you gave the mailbox [a password of its own](/docs/mail/webmail/security/#change-the-mailbox-password); - for a Mail Hosting mailbox, whoever manages the service sets the password and can [reset it](/docs/mail/mail-hosting/mailboxes/#reset-a-mailbox-password). - If the mailbox has two-factor authentication on, have its authenticator app ready. A mailbox's code is separate from your Coritan account's. ## Sign in with your Coritan account 1. Go to [Webmail](https://www.coritan.com/webmail). If you are signed in on coritan.com, your mailboxes open now and you are done. 2. Under the sign-in form, select `Continue with your Coritan account`. 3. Sign in to Coritan. Its sign-in brings you back to the same webmail page. Every mailbox your account can open is now in the account menu, your own coritan.gg mailbox first. When your account has no mailbox yet, webmail offers to [claim one](/docs/mail/webmail/claim-a-mailbox/) instead. ## Sign in with a mailbox's address 1. Go to [Webmail](https://www.coritan.com/webmail). 2. Enter the mailbox's address in **Email address** and its password in **Password**. 3. If a security check shows above the button, complete it. 4. Select **Sign in**. 5. If **Enter your code** opens, [enter the code](#enter-a-code). The mailbox opens on its inbox. At the top right, the button with the mailbox's initials opens the *account menu*, where you add, switch and sign out of mailboxes. Which password you use decides what opens: - Your own coritan.gg address with your Coritan password opens every mailbox your account can open, as signing in on coritan.com does. It does not sign you in to the rest of coritan.com. - A mailbox's own password, or one of its app passwords, opens that mailbox alone. **Open webmail** in a Mail Hosting mailbox's menu opens this form with the address filled in, so you type only the password. When that mailbox is open on this browser already, it comes on screen instead. When another mailbox is open, webmail offers to [add it](#add-another-mailbox). ## Enter a code After the password, webmail shows **Enter your code** when a code is needed. Codes change every 30 seconds. *The mailbox's code* : The mailbox has two-factor authentication on, and the card says `Two-factor sign-in is on for alex@example.com.` Type the code from the app you added the mailbox to in **Code**, then select **Sign in**. *Your Coritan account's code* : You signed in with your coritan.gg address and your Coritan password, and your account has two-factor authentication on. The card says `This is your Coritan account's mailbox.` Type the code from the authenticator app you use for Coritan in **Code**, then select **Sign in**. On your account's code step, you can use a recovery code when you do not have your authenticator app. Select **Use a recovery code instead** and type one of your Coritan recovery codes in **Recovery code**. Each recovery code works once, and **Use my authenticator app instead** switches back. A mailbox has no recovery codes of its own. To start again with another address or password, select **Back to the address and password**. ## Add another mailbox One browser keeps up to five mailboxes signed in with their own address and password, and you switch between them without signing in again. Mailboxes that open through your Coritan account do not count towards the five. 1. Open the account menu and select **Add another mailbox**. 2. Enter the other mailbox's **Email address** and **Password**, then select **Sign in**. Enter a code if it asks for one. The mailbox you added comes on screen, and a message says `alex@example.com added. Switch between mailboxes from the account menu.` To go back without adding one, select `Back to` followed by the address at the top of the card, or press Esc. Before the form opens, webmail saves any message you are writing to Drafts, in the mailbox you wrote it in. If it cannot save the message, the message stays open and webmail says why. ## Switch between mailboxes 1. Open the account menu. When you can open more than one mailbox, it lists them all under **Mailboxes**, with the unread count of each. 2. Select the mailbox to open. Webmail first saves any message you are writing to Drafts, as it does when you add a mailbox. You stay in the same folder, or go to the inbox if you had a conversation or a label open. Each browser tab keeps the mailbox it shows, so two tabs can show two mailboxes, and a reload keeps the same one. While several mailboxes are signed in, each tab's title includes the address. ## Sign out Open the account menu. It offers one way out for each way you signed in, and each item says what it ends: `Sign out of alex@example.com` : Ends the sign-in behind the mailbox on screen, when you signed in with an address and password. For a sign-in made with your Coritan password, the item names your coritan.gg address, and the mailboxes that sign-in opened close with it. Its second line says how many. **Sign out of all mailboxes** : Signs out of every mailbox signed in on this browser. It shows while you are not signed in on coritan.com and two or more mailboxes are signed in. `Sign out of Coritan` : Signs you out of Coritan on this browser, website included, and ends every sign-in made with your Coritan password. A mailbox signed in with its own password stays open. **Sign out of everything** : Ends your Coritan sign-in and every mailbox sign-in on this browser, and leaves the sign-in form empty. Use it on a computer other people use. It shows while you are signed in on coritan.com and a mailbox is signed in with its own password. Webmail saves any message you are writing to Drafts first. When a mailbox stays open, it comes on screen. Otherwise the sign-in card says what ended, such as `You have signed out.` or `You have signed out of everything on this browser.` **Sign out** on coritan.com also ends the webmail sign-ins made with your Coritan password. Signing out ends nothing on other browsers: to sign those out, use [the Sessions list](/docs/mail/webmail/security/#see-and-end-sessions). ## How long you stay signed in Through your Coritan account, webmail stays open for as long as you stay signed in on coritan.com in this browser. Each browser signed in to a mailbox with its address holds a *session*. A session lasts 30 days, and it ends sooner if you do not use it for 7 days. Closing the tab or the browser does not end it. > [!NOTE] > On a computer other people use, sign out when you finish. A session also ends: - when a sixth browser signs in to the same mailbox, which ends the one used longest ago; - when the mailbox's password changes, or its two-factor authentication goes on or off, except in the webmail session that made the change; - when someone signs the browser out from the **Sessions** list, or from the Mail Hosting service's **Webmail sessions…**; - when the mailbox is turned off or deleted. A session made with your coritan.gg address and your Coritan password also ends when your Coritan password changes, when your account turns on two-factor authentication after you signed in without a code, and when you sign out of Coritan on this browser. If a session ends while the page is open, the sign-in card comes back. Sign in again, and you return to where you were. ## Troubleshooting `That address and password do not match. Check both and try again.` : The address or the password is wrong. Enter the whole address, such as `alex@example.com`, and check the password. We give the same answer for a mailbox that is turned off, and for a mailbox from an organization's storefront, which signs in on that organization's own webmail. When the address is not on coritan.gg, the message adds that your Coritan password works only with your own coritan.gg address. `That code is not right, or it has expired. Enter the one your authenticator app shows now.` : Enter the code the app shows now. On the mailbox's code step, use the code for the mailbox. On your account's code step, use the code you use for Coritan. `That recovery code is not right, or you have used it already. Try another one.` : Type another of your Coritan recovery codes. Dashes and spaces in it do not matter. A message that starts `Too many attempts to sign in.` : After 10 failed attempts at one address within 15 minutes, or 20 from one IP address within 5 minutes, we refuse sign-ins for a while. Every failed attempt counts, whoever makes it, including a wrong code. The message says when you can try again, and the form stays off until then. `Five mailboxes are signed in on this browser, which is as many as it holds. Sign out of one to add another.` : Switch to a mailbox you no longer need here, sign out of it, then add the new one. `That mailbox is open here already. Switch to it from the account menu.` : The mailbox is one of those this browser opens. Open the account menu and select it. `Complete the security check first.` or `The security check did not pass. Complete it again, then sign in.` : Complete the check above the button, then select **Sign in** again. A check covers one attempt only. `The mail server did not answer, so you are not signed in yet. Try again in a moment.` : The server that holds the mailbox did not respond. Wait a moment, then sign in again. If it keeps happening, [contact support](/docs/support/). `The webmail could not reach its server` : Webmail could not load at all. Check your connection, then select **Try again**. A message that starts `Your session has ended.` or `The session for alex@example.com has ended.` : The session ended for one of the reasons under [How long you stay signed in](#how-long-you-stay-signed-in). Sign in again. Mailboxes still open on this browser show under **Still signed in on this browser**, and you can select one to open it. You forgot the password : For your coritan.gg mailbox, try your Coritan password: it is the mailbox's password until you give the mailbox one of its own. If you did and forgot it, sign in on coritan.com and open webmail. The **Security** tab then sets a new mailbox password with your Coritan password alone, as [Change the mailbox password](/docs/mail/webmail/security/#change-the-mailbox-password) shows. For a Mail Hosting mailbox, ask whoever manages the service to [reset its password](/docs/mail/mail-hosting/mailboxes/#reset-a-mailbox-password). ## Related - [How webmail works](/docs/mail/webmail/) - [Open and share mailboxes in webmail](/docs/mail/webmail/mailboxes-and-sharing/) - [Protect your webmail mailbox](/docs/mail/webmail/security/) - [Connect a mail app to Mail Hosting](/docs/mail/mail-hosting/connect-a-mail-app/) # Get a coritan.gg mailbox > Take an address on coritan.gg when you create your Coritan account, or claim one later in webmail. Source: https://www.coritan.com/docs/mail/webmail/claim-a-mailbox/ In the dashboard: - /webmail: https://www.coritan.com/webmail A Coritan account can have one mailbox of its own on `coritan.gg`, such as `alex@coritan.gg`. You take the address when you create your account, or you claim it later in [webmail](https://www.coritan.com/webmail). You read the mail in webmail, and in any mail app over IMAP. This page covers claiming the mailbox later. To take one while you sign up, see [Sign up with a new mailbox](/docs/get-started/create-an-account/#sign-up-with-a-new-mailbox). ## Before you begin - Be signed in on coritan.com. Webmail offers the address only to your Coritan sign-in, so a mailbox you opened with its own address and password cannot claim one. - Have your Coritan password. - Check that you have no mailbox yet. An account can claim one mailbox, and a mailbox you took at sign-up is that one. ## Choose a name The name is the part before `@coritan.gg`. It must: - be 3–40 characters long; - use only letters, digits, dots, dashes and underscores, which webmail reads in lowercase; - start and end with a letter or a digit; - have no two dots in a row and no plus sign. Some names are reserved, such as `support`, `billing` and `webmail`. A name is also taken when a Coritan account already signs in with that address. ## Claim your mailbox 1. Go to [Webmail](https://www.coritan.com/webmail). While your account has no mailbox, webmail shows `Claim your @coritan.gg address`. 2. Type a name in **Your address**, or select one of the ideas under the field. Webmail checks the name as you type, and the line under the field says `alex@coritan.gg is free.` when it is. 3. Enter your Coritan password in the field under it. It confirms that it is you, and it becomes the mailbox's password too. 4. Select **Create my mailbox**. A message says `alex@coritan.gg is yours.`, and your new inbox opens. You go on signing in to Coritan with the email you use now. The new address is where your mail arrives. The same page lists any invitation to a mailbox someone shared with you, with **Accept** and **Decline**. Under **Have a mailbox already?**, **Add a mailbox** adds one you manage to your account, as [Add a mailbox to your account](/docs/mail/webmail/mailboxes-and-sharing/#add-a-mailbox-to-your-account) describes. ## What the mailbox comes with - 5 GB of storage by default. The line under the folder list shows how much you use. - Up to 100 messages sent an hour by default. - Sign-in from mail apps over IMAP and SMTP, with the mailbox's address and password. See [Set up mail apps and devices](/docs/mail/webmail/mail-apps/). ## The mailbox password The mailbox password starts as your Coritan password. When you [change your account password](/docs/account/password/#change-your-password), the mailbox password changes with it, and mail apps need the new one. You can give the mailbox a password of its own on the **Security** tab of webmail settings. From then on it stays as it is when your account password changes. See [Change the mailbox password](/docs/mail/webmail/security/#change-the-mailbox-password). ## Troubleshooting `That address is already taken.` : Someone has a mailbox with that name, or a Coritan account signs in with that address. Choose another name. `That name is reserved.` : We keep that name back. Choose another name. `Pick at least 3 characters.`, `Keep it under 40 characters.`, `Two dots in a row are not allowed.` or a message about the characters to use : The name breaks one of the [naming rules](#choose-a-name). Change it until the line under the field says it is free. `Pick a free address first.` : You selected **Create my mailbox** with no name in **Your address**. Type a name, and wait until the line under the field says it is free. `Could not check that name just now. You can still try it.` : We could not check the name. Select **Create my mailbox** anyway, or type the name again after a moment. `Incorrect account password.` : Enter the password you sign in to your Coritan account with. `The mail service is not accepting new mailboxes right now. Try again in a moment.` : We could not create the mailbox at that moment. Nothing was created, so try again later. `This account already has a mailbox.` : Your account has its mailbox already, and webmail opens it. `No mailbox in your account yet` : This site does not give out addresses, so the page offers only **Add a mailbox** and the invitations waiting for you. For mailboxes on a domain you own, see [Mail Hosting](/docs/mail/mail-hosting/). A message that starts `Could not create the mailbox:` and says there were too many requests : An account can try to claim a mailbox 5 times an hour. Wait, then try again. `Your account could not be read` : Webmail could not load what your account can claim. Select **Try again**. ## Related - [Create an account](/docs/get-started/create-an-account/) - [How webmail works](/docs/mail/webmail/) - [Set up mail apps and devices](/docs/mail/webmail/mail-apps/) ## With the API The claim itself happens in webmail only. The public API has the two checks that the sign-up form makes before an account exists. Neither needs authentication. Ask whether the storefront offers a mailbox, and on which domain, with [`GET /auth/mailbox-domain`](/docs/api/reference/client/authentication/#op-get-api-v1-auth-mailbox-domain): ```bash curl https://api.coritan.com/api/v1/auth/mailbox-domain ``` ```json {"enabled": true, "domain": "coritan.gg"} ``` Check a name with [`GET /auth/mailbox-availability`](/docs/api/reference/client/authentication/#op-get-api-v1-auth-mailbox-availability): ```bash curl "https://api.coritan.com/api/v1/auth/mailbox-availability?local_part=alex" ``` ```json {"available": false, "reason": "taken", "detail": "That address is already taken", "address": "alex@coritan.gg"} ``` `reason` is `ok`, `invalid`, `reserved` or `taken`, and `detail` says why a name cannot be used. When the storefront offers no mailbox, the domain check answers `"enabled": false` and the availability check answers `404` `Mailboxes are not offered on this storefront`. One IP address can check up to 60 names a minute. To create an account together with its mailbox, send `mailbox_local_part` to `POST /auth/register`, as [Create an account](/docs/get-started/create-an-account/#with-the-api) shows. ## API - `GET /api/v1/auth/mailbox-domain`: Mailbox domain (https://www.coritan.com/docs/api/reference/client/authentication/#op-get-api-v1-auth-mailbox-domain) - `GET /api/v1/auth/mailbox-availability`: Live check behind the sign-up form's username field (https://www.coritan.com/docs/api/reference/client/authentication/#op-get-api-v1-auth-mailbox-availability) # Read and search mail in webmail > Move between folders and labels, open a conversation, act on messages, and find mail with search operators in webmail. Source: https://www.coritan.com/docs/mail/webmail/read-and-search/ In the dashboard: - /webmail/…: https://www.coritan.com/webmail - /webmail/…/…: https://www.coritan.com/webmail Webmail groups a message with its replies into one *conversation*. You pick a folder or label in the sidebar, open a conversation from the list, and act on it from the toolbar above it. Each folder has its own address in webmail, such as `https://www.coritan.com/webmail/inbox`, and so does each conversation, so you can bookmark one or open it in another tab. ## Before you begin - Open the mailbox in webmail. See [Sign in to webmail](/docs/mail/webmail/sign-in/). - Someone who **Can only read** a shared mailbox can read and search it, but cannot move, label, delete or mark its mail. ## Folders The sidebar lists the folders. In a narrow window it is hidden: select the menu button (**Folders**) at the top left to open it. | Folder | What it holds | | --- | --- | | **Inbox** | New mail. The number beside it counts unread conversations. | | **Starred** | Conversations you starred. | | **Important** | Conversations marked important. | | **Snoozed** | Conversations you snoozed, until they come back. | | **Sent** | Messages you sent. | | **Scheduled** | Messages waiting to be sent later. The number beside it counts them. | | **Drafts** | Messages you have started and not sent. The number beside it counts them. | | **Archive** | Conversations you archived. | | **All mail** | Everything except Spam and Trash. | | **Spam** | Mail the mail server thinks is spam, and mail you reported. The number beside it counts unread conversations. | | **Trash** | Mail you deleted. | Under **Labels** are your labels, each with its number of unread conversations. See [Sort mail with labels and filters](/docs/mail/webmail/labels-and-filters/). The foot of the sidebar shows how much of the mailbox's storage you use, with the way to **Contacts** and **Settings** below it. New mail appears on its own. The status at the top right reads **Live** while new mail shows the moment it arrives. When that connection is down, webmail checks every 30 seconds and the status reads **Every 30 s**; it reads **Syncing** while it checks. To check now, select the arrows button (**Refresh**) above the list. ## The conversation list Each row shows who wrote, the subject, a preview of the text and the date. A paperclip means the conversation has an attachment, and its labels show before the subject. A row can also say when a scheduled message goes or when a snoozed conversation comes back, and a crossed-out bell marks a muted conversation. With **Density** set to **Compact** in [settings](/docs/mail/webmail/settings/#reading-and-sending), rows leave out the preview. The list shows 50 conversations at a time, newest first. Use the arrows at the top right (**Newer** and **Older**) to move between pages. - To open a conversation, select its row. - To star it, select the star at the start of the row. The bookmark beside the star marks it as important. - To act on one conversation, point at its row. Buttons appear on the right to archive it, delete it, mark it read or unread, and snooze it. - To act on several, tick the box at the start of each row, or the box above the list (**Select all**) to select the whole page. The toolbar then shows what you can do with them. - To move conversations, drag them onto **Inbox**, **Archive**, **Spam** or **Trash** in the sidebar. Dropping them on a label adds that label. ## Open a conversation The conversation opens with its messages oldest first. The newest message, any unread ones and any drafts are open, and the others show as one line each: select a line to open that message. Opening a conversation marks its messages as read. - To see every address a message went to, with its date and subject, select the recipients line under the sender's name. - Attachments are listed under the message. Select one to open it in a new tab, or its download button to save it. - To answer, use **Reply**, **Reply all** or **Forward** under the newest message. **Reply all** shows when the message went to more than one person. See [Write and send email in webmail](/docs/mail/webmail/write-and-send/). - To go to the next conversation in the list without going back, use **Newer** and **Older** at the top right. - To go back to the list, select the arrow at the top left (**Back to the list**). When you archive, delete or move the conversation you are reading, the next one in the list opens. On a phone, you go back to the list instead. A line above the messages says when a snoozed conversation comes back, with **Unsnooze**, and when a muted one skips your inbox, with **Unmute**. For a message waiting in **Scheduled**, it says when the message goes, with **Cancel send**. See [Schedule a message](/docs/mail/webmail/write-and-send/#schedule-a-message). In **Spam**, it says the conversation is in Spam, with **Not spam**. See [Get mail out of Spam](#get-mail-out-of-spam). ### Images in a message A message can load pictures from the web, which tells the sender when you read it. Unless the mailbox is set to show them, webmail hides them and says `Images are hidden so the sender cannot see when you read this.` above the message. Select **Show images** to see them in this message, or `Always show from` followed by the sender's name to show them from that sender every time. To change the default, see [Change webmail settings](/docs/mail/webmail/settings/#reading-and-sending). ## Act on conversations These actions are in the toolbar of an open conversation, and in the toolbar above the list when you select conversations. Hover over a button to see its name. | Action | What it does | | --- | --- | | **Archive** | Takes the conversation out of the Inbox. It stays in **Archive** and **All mail**. | | **Report spam** | Moves it to Spam. In the Spam folder, **Not spam** moves it back to the Inbox and adds its senders to your contacts. See [Get mail out of Spam](#get-mail-out-of-spam). The mail server's spam filter learns from both. | | **Delete** | Moves it to Trash. In Trash or Spam, the button reads **Delete forever** and removes the mail for good. | | **Mark as read**, **Mark as unread** | Changes whether it counts as unread. In an open conversation, **Mark as unread** also takes you back to the list. | | **Snooze** | Hides it until a time you choose. See [Snooze a conversation](#snooze-a-conversation). | | **Mark as important** | Adds it to **Important**. On an important conversation, the button reads **Mark as not important**. | | **Move to** | Moves it to Inbox, Archive, Spam, Trash or a label. | | **Labels** | Adds or removes labels. | The menu at the end of the toolbar (**More actions**) holds the rest: **Add star** and **Remove star**, **Mute** or **Unmute**, and **Move to Inbox** outside the Inbox. In an open conversation it also holds **Expand all** and **Print all**. On a phone, **Snooze…** and **Mark as important** move into this menu too. After most actions, a bar at the bottom of the page says what happened, such as `2 conversations archived`. Select **Undo** within 8 seconds to reverse it, or press z. **Delete forever** cannot be undone. In **Scheduled**, the toolbar offers **Cancel send** in place of moving or deleting a message. ## Act on one message Each open message has its own menu: the three dots at its top right (**Message actions**). It holds: - **Reply all** and **Forward**. - **Mark as unread**, which also takes you back to the list. - **Download original (.eml)**, which saves the whole message as a file. - **Unsubscribe**, when the message is from a mailing list that offers it. It opens the sender's page for leaving the list, or starts an email that asks to leave. - `Add` followed by the sender's name and `to contacts`, while the sender is not in your contacts. See [Keep an address book in webmail](/docs/mail/webmail/contacts/). - **Filter messages like this**, and `Block` followed by the sender's name, for whoever manages the mailbox. See [Create a filter](/docs/mail/webmail/labels-and-filters/#create-a-filter) and [Block a sender](/docs/mail/webmail/labels-and-filters/#block-a-sender). - **Report spam**, or **Not spam** in the Spam folder, which moves the message to the Inbox and adds its sender to your contacts. - **Delete this message**, which moves only this message to Trash. In Trash or Spam it reads **Delete this message forever**. A draft in a conversation has **Edit draft** and **Discard draft…** under it instead. ## Get mail out of Spam The mail server files mail it takes for spam in **Spam**. To move a message you want to the Inbox, and let its sender's next messages through: 1. Open **Spam** and select the conversation. The line above it reads `This conversation is in Spam.` and says who **Not spam** adds to your contacts. 2. Select **Not spam** on that line. The same button is in the toolbar, in the message's menu (**Message actions**), and in the toolbar above the list when you select conversations there. Webmail moves the conversation to the Inbox and adds each sender to your contacts, with the name their message shows. The bar at the bottom says who it added, such as `We added Alex to your contacts.` Select **Undo** within 8 seconds to move the conversation back to Spam and delete the contacts it added. Someone who **Can only read** the mailbox sees the line without **Not spam**. Mail from a contact reaches your Inbox even when the spam filter takes it for spam, as long as it passes the DMARC check of the domain in its From address. A message that fails that check, such as one that only pretends to come from a contact, still goes to Spam. The mail server looks only at a contact's first email address, so webmail also adds a sender who is the second address of a contact, as a contact of their own. The spam filter learns from each **Not spam** and **Report spam** too. To let the spam filter judge a sender's mail again, delete them from your contacts ([Change or delete a contact](/docs/mail/webmail/contacts/#change-or-delete-a-contact)). Mail from a [blocked sender](/docs/mail/webmail/labels-and-filters/#block-a-sender) goes to Spam even when they are a contact, until you unblock them. ## Snooze a conversation Snoozing takes a conversation out of the Inbox until a time you choose. 1. Select the clock button (**Snooze**) on the conversation's row, in the toolbar after you select it, or in the open conversation. On a phone, select **Snooze…** in **More actions**. 2. Choose **Later today**, **Tomorrow**, **This weekend** or **Next week**. The menu shows the time each one means, in your device's time zone. **Later today** means 6 pm and shows until 5 pm. **Tomorrow**, **This weekend** and **Next week** mean 8 am on the next day, on Saturday and on Monday, and **This weekend** shows from Monday to Thursday. 3. For another time, select **Pick date and time…**, set **Date and time**, then select **Snooze**. The conversation moves to **Snoozed**, and the bar at the bottom says until when. At that time, it comes back to the Inbox, with its newest message from someone else marked unread. This happens even when webmail is closed. The time must be at least a minute ahead and within a year. To bring it back sooner, open it and select **Unsnooze**. ## Mute a conversation Muting keeps a busy conversation out of your Inbox. 1. Open the conversation, or select it in the list. 2. Select **More actions**, then **Mute**. The conversation moves to Archive, and new messages in it go to Archive instead of staying in the Inbox. The conversation shows `This conversation is muted, so new messages in it skip your inbox.` To stop, select **Unmute** there, or in the same menu. A mailbox can mute up to 500 conversations. ## Search your mail 1. Select the search box at the top. 2. Type words to look for, with any of the operators below, then press Enter. While you type, the box suggests people you have mail from, operators, your recent searches, and the views **Unread**, **Starred** and **With attachments**. Words on their own match the senders, the recipients, the subject and the text of each message. Results are listed newest first, under a heading that says where the search ran, such as `Results for “invoice” in all mail`. **Search all mail** widens a search to all mail, and **Clear search** ends it. Where a search looks depends on where you start it: - From **Inbox** or **All mail**, it looks through all mail except Spam and Trash. - From another folder or a label, it looks only there. - An `in:` or `label:` operator chooses the folder, wherever you start. `in:all` still leaves out Spam and Trash, so use `in:spam` or `in:trash` to search those. To build a search without operators, select the sliders button (**Show search options**) in the search box. Fill in **From**, **To**, **Subject** or **Has the words**, set a **Size**, pick a day for **Date within**, choose where to look in **Search in**, and tick **Has attachment** or **Unread only**. Then select **Search**. **Date within** finds mail from that long before the day to that long after it. **Create filter** turns the same fields into a new filter; see [Create a filter](/docs/mail/webmail/labels-and-filters/#create-a-filter). ### Search operators | Operator | Finds | | --- | --- | | `from:alex@example.com` | Mail from that sender. Part of an address or a name also works. | | `to:`, `cc:`, `bcc:` | Mail sent to, copied to or blind copied to that address. | | `subject:invoice` | Mail with that word in the subject. | | `has:attachment` | Mail with an attachment. | | `filename:report.pdf` or `attachment:report.pdf` | Mail with an attachment of that name. | | `is:unread`, `is:read` | Unread mail, or mail you have read. | | `is:starred` | Starred mail. | | `is:important` | Mail marked important. | | `is:draft` | Drafts. | | `is:answered` | Mail you replied to. | | `in:sent`, `label:receipts`, `folder:archive` | Mail in one folder or label. The words are `inbox`, `sent`, `drafts`, `archive`, `spam`, `trash`, `starred`, `important`, `unread` and `all`, or a label's name. `in:snoozed` and `in:scheduled` work once something is snoozed or scheduled. | | `after:2026-01-01` or `since:2026-01-01` | Mail from that day or later. | | `before:2026-01-31` | Mail from before that day. | | `on:2026-01-15` | Mail from that day. | | `larger:5M` or `size:5M` | Mail larger than that size. Use `k`, `m` or `g` for kilobytes, megabytes or gigabytes. | | `smaller:500k` | Mail smaller than that size. | - Write dates as `2026-01-31`. A day runs from midnight to midnight UTC. - Put a phrase in quotes to find it whole, such as `"quarterly report"` or `subject:"quarterly report"`. A label name with spaces goes in quotes too: `label:"Travel plans"`. - Put `-` before an operator to leave out what it finds, such as `-from:alex@example.com` or `-in:sent`. - Webmail searches for an operator it does not know, or a value it cannot read, as ordinary words. ## Troubleshooting `Could not load this folder` or `Could not open this conversation` : Webmail could not reach the mail server. Select **Try again**. If it keeps happening, see [Troubleshoot mail](/docs/mail/troubleshooting/). `We could not find this conversation` : The conversation may have been deleted, or the link may be wrong. Select the button under the message to go back to the list. A message that starts `No conversations match` : Check which folder you started from, since a search from a folder or a label looks only there. Select **Search all mail**, or use fewer words. The status reads **Every 30 s** : The live connection to the mail server is down, so webmail checks for new mail every 30 seconds and keeps trying to reconnect. Mail still arrives. If you have many webmail tabs open, close the ones you do not need and reload the page. Pictures in a message are missing : Webmail hides pictures that load from the web. Select **Show images** above the message. Mail from a sender you marked **Not spam** still goes to Spam : The mail server lets a contact's mail past the spam filter only when the message passes the DMARC check of the domain in its From address, and only for the contact's first email address. A sender whose domain has no DMARC record, or who sends through a service that record does not cover, can still land in Spam, so ask them to fix their DMARC setup. Mail from a [blocked sender](/docs/mail/webmail/labels-and-filters/#block-a-sender) goes to Spam even when they are a contact: select **Unblock** beside them in **Blocked senders**. `This message is too large to show in full.` : Webmail shows the start of a very long message. Select **Download the original** to save the whole message. A message that ends `you can read this mailbox but not change it.` : The mailbox was shared with you so that you can only read it, and webmail could not move, label or mark its mail. It can show as you open unread mail, which webmail tries to mark as read. Ask the owner to let you send from it. A message that ends `this mailbox's settings are its owner's to change.` : You chose `Always show from` in a mailbox shared with you. Only whoever manages the mailbox chooses whose images always show. Select **Show images** instead. `Pick a time at least a minute from now.` or `Pick a time within a year.` : Choose a snooze time between one minute and one year from now. A message that ends `a mailbox can mute 500 conversations; unmute some first.` : Unmute conversations you no longer need muted, then try again. A message that starts `Could not update` and ends `Refresh and try again.` : Some of the messages changed on the mail server while you acted on them. Select **Refresh**, then try again. ## Related - [Webmail keyboard shortcuts](/docs/mail/webmail/keyboard-shortcuts/) - [Sort mail with labels and filters](/docs/mail/webmail/labels-and-filters/) - [Write and send email in webmail](/docs/mail/webmail/write-and-send/) # Write and send email in webmail > Compose a message, reply or forward, attach files, use signatures and templates, schedule a message and undo a send in webmail. Source: https://www.coritan.com/docs/mail/webmail/write-and-send/ In the dashboard: - /webmail/…: https://www.coritan.com/webmail Webmail writes each message in a compose window in the corner of the page. The window saves a draft while you type, and holds a sent message for a few seconds so you can take it back. You can also schedule a message to go later, start one from a template, and choose among the mailbox's signatures. ## Before you begin - Open the mailbox in webmail at [www.coritan.com/webmail](https://www.coritan.com/webmail). [Sign in to webmail](/docs/mail/webmail/sign-in/) explains how. - To send from a mailbox someone shared with you, you need **Can read, organize and send**. With **Can only read**, webmail still opens a compose window, but it cannot save or send the message. ## Write a new message 1. Select **Compose** at the top of the folder list, or press c. 2. Add the people you are writing to in **To**. [Add recipients](#add-recipients) explains how the box works. 3. Enter a **Subject**. 4. Write the message in the large box. 5. Select **Send**, or press Ctrl+Enter (⌘+Enter on a Mac). If the subject is empty, webmail asks `Send without a subject?`. Select **Send anyway**, or **Add a subject** to go back and write one. The window closes, and the message goes when the undo time runs out. See [Undo a send](#undo-a-send). On a narrow screen, such as a phone or a tablet held upright, the window fills the screen. Its title bar has **Minimise**, which folds it down to its title, **Full screen** (**Exit full screen** once it fills the screen) and **Save and close**. A minimised window shows **Open** and **Save and close**. Esc minimises a window that does not fill the screen. You can have several messages open at once. Selecting an email address that a message links to also starts a new message to that address. ## Add recipients - Type an address, then press Enter, a comma or a semicolon, or move to another box. The address becomes a chip. - As you type, webmail suggests people and groups from your address book first, then people the mailbox has written to or heard from. The first suggestion is highlighted, and Enter or Tab takes it. Use the arrow keys to pick another. When you have typed a whole address, it comes first, marked **Use this address**. - Choosing a group from the address book adds each of its members. - To add several addresses at once, paste them separated by commas, semicolons or new lines. - To change an address, select its chip. To remove it, select the cross on the chip. Backspace in an empty box brings the last address back into the box to edit. - To copy people in, select **Cc** or **Bcc** beside **To**. People in **Bcc** get the message, and the other recipients do not see their addresses. Text that is not a whole address, such as a name with no domain, stays in the box with the reason under it, for example `“alex” is not an email address.` Webmail does not send until you correct it or delete it. ## Format the message The toolbar under the message formats the text: bold, italic, underline, strikethrough, bulleted and numbered lists, quotes and links. **Remove formatting** clears the formatting of the selected text. Hover over a button to see its name. To hide or show the toolbar, select the T icon (**Formatting options**). Ctrl+B, Ctrl+I and Ctrl+U work too. To add a link: 1. Select the text for it, then select **Insert link** or press Ctrl+K. 2. Enter the **Web or email address**, such as `example.com`. An email address becomes a link that starts a message to it. 3. With no text selected, also enter the **Text to show**, or leave it empty to show the address itself. 4. Select **Insert link**. For a link that is already there, the box is called **Edit link** and offers **Save link** and **Remove link**. A picture you paste or drop into the message is attached as a file. Text you paste keeps its structure, such as paragraphs, lists, tables and links. It loses the styling it had, such as fonts and colours. ## Attach files 1. Select the paperclip (**Attach files**) at the bottom of the window and choose one or more files. You can also drop files onto the window. 2. Wait while each file uploads. Its row shows how far it has got. Each file can be up to 25 MB, unless your mailbox has another limit: **Largest attachment** on the **Mail apps** tab of settings shows it. To take a file off, or stop one that is uploading, select the cross beside it. You cannot send while a file is still uploading. ## Reply or forward Open the conversation. Under its newest message, select one of these: - **Reply** answers the sender, or the reply-to address the message names. For a message you sent, it goes to the people you sent it to. - **Reply all** also copies everyone else in **To** and **Cc**, apart from you. Under the message, it shows only when the message went to more than one person. - **Forward** sends the message on to people you add, with its attachments. Each message also has a reply arrow (**Reply**) at its top right, and **Reply all** and **Forward** in its **Message actions** menu. In an open conversation, r, a and f reply, reply to all and forward. The subject starts with `Re:` or `Fwd:`, and webmail quotes the original message under the space where you write. ## Choose the From address When the mailbox can send from more than one address, the window has a **From** list. It holds the mailbox's own address and each alias that delivers to it. A new message starts from the mailbox's own address. A reply or forward starts from the address the message was sent to, or the one you sent it from, when the mailbox can send as that address. The name and reply-to address that go with each address come from settings: see [Set your name and reply-to address](/docs/mail/webmail/settings/#set-your-name-and-reply-to-address). ## Use a signature A new message starts with the signature that settings choose for new mail from its **From** address. A reply or forward starts with the one chosen for replies and forwards. The signature goes under a new message, and above the quoted text of a reply or forward. - To use another one for this message, select the pen icon (**Signature**) and choose it, or choose **No signature**. - **Manage signatures** minimises the window and opens settings, where you write and name them. Only people who manage the mailbox see it. - Choosing another **From** address swaps in that address's signature, unless you have edited the signature in the message. [Add a signature](/docs/mail/webmail/settings/#add-a-signature) explains how to set them up. ## Use a template A *template* is a message you reuse, such as an answer you often send. A mailbox keeps up to 100. Webmail keeps them in a folder named Templates in the mailbox, so mail apps show them there as drafts. - To save one, write the message, select the document icon (**Templates**), then **Save this message as a template**. The template keeps the subject, the text you wrote and the attachments. It leaves out the signature and any message you are replying to or forwarding. Webmail shows `Saved “Monthly report” as a template.`, with your subject. - To use one, select **Templates** and choose it by its subject. Its text goes in where the cursor is, or at the top if you have not been in the message yet, and its attachments are added. When **Subject** is empty, the template's subject fills it. - To delete one, select **Templates**, then the bin beside it (**Delete template**), and confirm with **Delete template**. Messages you already wrote from it stay as they are. ## Drafts While you write, webmail saves the message to **Drafts** a few seconds after you stop typing. It starts once the message has a recipient, a subject, some text or an attachment. The bottom of the window shows `Saving…`, then `Draft saved`. - To keep a message for later, select **Save and close**, the cross at the top right of the window. Webmail saves it and shows `Draft saved.` - To throw a message away, select the bin (**Discard draft**), then select it again within 3 seconds, while it reads **Click again to discard**. That also deletes the saved draft. To finish a saved draft, open it from **Drafts** and select **Edit draft**, under the message or at its top right. It opens in a compose window, and saving or sending it replaces the draft. To delete a saved draft, select **Discard draft…** and confirm with **Discard draft**. It is deleted for good, with its attachments. When you switch to another mailbox, webmail saves each open message to **Drafts** in the mailbox you wrote it in, then closes the windows. A note says which mailbox holds them. ## Schedule a message 1. Write the message. 2. Select the arrow beside **Send** (**Schedule send**). 3. Choose **Tomorrow morning** (8 am), **Tomorrow afternoon** (1 pm) or **Monday morning** (8 am next Monday). The menu shows the date and time each one means, in your device's time zone. On a Sunday, **Monday morning** is the same as **Tomorrow morning**, so the menu leaves it out. 4. For another time, select **Pick date and time…**, set **Date and time**, then select **Schedule send**. The window closes, and a bar at the bottom of the page says `Scheduled for` and the time. Select **Undo** within 8 seconds to cancel the send and open the message again. The message waits in the **Scheduled** folder. It goes at the time you chose, or up to about a minute after, even when webmail is closed. The time must be at least a minute ahead and at most 366 days ahead. A mailbox can have up to 200 messages waiting. ### Change or cancel a scheduled message Webmail does not change a message while it waits, so cancel its send first: - In **Scheduled**, point at the message's row and select **Cancel send**. To cancel several, select their rows, then **Cancel send** at the top of the list. Each message moves to **Drafts**, and webmail shows `Sending canceled. The message is in Drafts.` - Open the message. The line above it says `We send this message on` and the time, with **Cancel send**. Under the message, **Cancel send and edit** opens it in a compose window. **Cancel send and delete…** deletes it for good once you confirm with **Cancel send and delete**, and **Keep it scheduled** leaves it waiting. While the message is going out, the line says `We are sending this message now.` and you can no longer cancel it. If someone deletes the message, or moves it out of **Scheduled**, in a mail app before its time, it is not sent. ### When a scheduled message is not sent A scheduled message goes out as the person who scheduled it. It is not sent if that person can no longer send from the mailbox by then, for example because the owner removed their access or changed it to **Can only read**. If the mail server refuses the message, webmail tries twice more, two minutes apart, then gives up. Either way, the message goes back to **Drafts**, marked `Not sent`. Its conversation says `We could not send this message:` and the reason. Select **Move to Drafts**, which makes it an ordinary draft again, then **Edit draft** to correct it and send it. ## Undo a send When you select **Send**, the window closes, and a bar at the bottom of the page says `Sending…` with **Undo**. Select **Undo**, or press z, before the time runs out, and the message opens again in a compose window. **Dismiss**, the cross on the bar, hides the bar, and the message still goes. You have 5 seconds unless you change it. To change it, go to **Undo send** under **Reading and sending** on the **General** tab of settings, and choose **Off**, **5 s**, **10 s**, **20 s** or **30 s**. With **Off**, a message goes at once and you cannot take it back. See [Reading and sending](/docs/mail/webmail/settings/#reading-and-sending). The message waits in your browser until the time runs out. If you close or reload the tab before then, it is not sent. The last draft webmail saved before you selected **Send** stays in **Drafts**. Once the message has gone, webmail shows `Message sent.`, and you cannot take it back. If you switched to another mailbox in the meantime, the message still goes from the mailbox you wrote it in, and webmail names that mailbox: `Message sent from` followed by its address. ## Troubleshooting `Add at least one recipient before sending.` : Add an address in **To**, **Cc** or **Bcc**. A message such as `“alex” is not an email address.` : What you typed in **To**, **Cc** or **Bcc** is not a whole address. Correct it, for example by adding the domain, or delete it. `Wait for the attachments to finish uploading.` : A file is still uploading. Wait for it to finish, or remove it. A message that starts `Could not attach` : The file did not upload, and its row says `Not attached`. If the message ends `is larger than 25 MB.`, the file is over the limit the message names, so share it with a link instead. Otherwise remove the file and attach it again. A message that starts `Could not send the message:` or `Could not schedule the message:` : The mail server refused the message. It opens again in a compose window with everything you wrote. Read the reason, correct what it names and try again. If you had switched to another mailbox, the message names that mailbox and does not open again: switch back, and look in its **Drafts** for the last copy webmail saved. `Could not send the message: this mailbox cannot send as` followed by an address : The **From** address is no longer one the mailbox can send from, for example because its alias was removed. Choose another **From** address and send again. The same words after `Could not schedule the message:` or `Could not save the draft:` mean the same. A message that starts `Could not save the draft, so the window stays open` : Webmail could not save the message when you closed the window. Read the reason, then try again. A message with the words `can read this mailbox but not change it` : You can only read this mailbox, so webmail cannot save or send a message from it. Ask its owner to give you **Can read, organize and send**. `Pick a time at least a minute from now.` or `Pick a time within a year.` : Choose a time between one minute and 366 days from now. A message that says `A mailbox can have 200 messages waiting to be sent` : Cancel a scheduled message you no longer need, or wait until some have gone. `Could not cancel the send: it has already been sent.` or `Could not cancel the send: it is being sent right now.` : The message went, or was going, before you cancelled it. A message that ends `keeps 100 templates; delete one first.` : Delete a template you no longer use, then save the new one. `Write a subject or some text first: a template keeps both for your next message.` : A template needs a subject or some text of your own. The signature and any quoted message do not count. ## Related - [Change webmail settings](/docs/mail/webmail/settings/) - [Keep an address book in webmail](/docs/mail/webmail/contacts/) - [Read and search mail in webmail](/docs/mail/webmail/read-and-search/) - [Open and share mailboxes in webmail](/docs/mail/webmail/mailboxes-and-sharing/) - [Webmail keyboard shortcuts](/docs/mail/webmail/keyboard-shortcuts/) # Keep an address book in webmail > Add, find, group, import and export the contacts of a mailbox in webmail, and write to a contact or a whole group. Source: https://www.coritan.com/docs/mail/webmail/contacts/ In the dashboard: - /webmail/contacts: https://www.coritan.com/webmail/contacts Each mailbox has an *address book*, kept on the mail server with the mailbox. Everyone who can open the mailbox sees the same contacts, and a new message suggests addresses from them. Phones and mail apps that sync contacts reach the same address book, as [Set up mail apps and devices](/docs/mail/webmail/mail-apps/) describes. ## Before you begin - Open the mailbox in webmail. Each mailbox has its own contacts, so [switch to the right one](/docs/mail/webmail/sign-in/#switch-between-mailboxes) first. - To add, change, import or delete contacts, you need to be able to send from the mailbox. Someone who **Can only read** it can search the contacts, open them and export them. ## Open your contacts Select **Contacts** in the folder list, under your labels. On a narrow screen, open the folder list first with **Folders**, the menu button at the top left. The page is at [www.coritan.com/webmail/contacts](https://www.coritan.com/webmail/contacts) for the mailbox on screen. The page lists each contact with its first email address and phone number. **All contacts** above the list shows everyone, and each group's button beside it shows only that group's members. The group you select is in the page's address, so a reload keeps it. To go back to your mail, select the arrow beside the **Contacts** heading. ## Add a contact 1. Select **New contact**. 2. Enter a **First name** and a **Last name**. 3. Under **Email**, enter an address and choose **Work**, **Home** or **Other** beside it. Select **Add an address** for another one. 4. Under **Phone**, enter a number and choose **Mobile**, **Work**, **Home** or **Other**. Select **Add a number** for another one. 5. Optionally, fill in **Company**, **Job title** and **Notes**. Under **Groups**, tick each group the contact belongs to. **Groups** shows once the address book has a group. 6. Select **Add contact**. We show `Added` followed by the name and `to your contacts.` A contact needs at least a name, a company, an email address or a phone number, and holds up to 10 addresses and 10 numbers. To add the sender of a message you are reading, open the message's menu, **Message actions** at its top right, and select `Add` followed by the sender's name and `to contacts`. The menu offers it only while the sender is not in the address book. Mail from a contact's first email address reaches your Inbox even when the spam filter takes it for spam, as long as it passes the DMARC check of the sender's domain. That is why **Not spam** adds the sender to your contacts ([Get mail out of Spam](/docs/mail/webmail/read-and-search/#get-mail-out-of-spam)). Delete the contact to let the spam filter judge their mail again. ## Find a contact Type in **Search contacts** above the list. The list keeps each contact that has every word you typed in its name, company, job title, email addresses or phone numbers. A phone number matches however it is spaced. To search one group only, select the group first. Esc clears the search. A long list shows 200 contacts at first, and the button at its end shows the next ones. ## Change or delete a contact 1. Select the contact's row. **Edit contact** opens. 2. Change what you need and select **Save changes**. To delete the contact, select **Delete contact** in the same dialog, then **Delete contact** again to confirm. Phones and mail apps that sync the address book lose it too. If you close the dialog with changes you have not saved, webmail asks first. Select **Keep editing** to go back, or **Discard** to close it. Someone who **Can only read** the mailbox sees the contact's details instead, with **Close**. ## Write to a contact Select the envelope button at the end of a contact's row. A new message opens with the contact's first email address in **To**. In any message, start typing a name or an address in **To**, **Cc** or **Bcc**, and pick a contact from the suggestions. When you type a group's name, the suggestions offer the group with the number of people in it, and picking it adds each of them. See [Add recipients](/docs/mail/webmail/write-and-send/#add-recipients). ## Sort contacts into groups A *group* is a named list of contacts, such as your team. A contact can be in more than one. 1. Select **New group** after the group buttons. 2. Enter a **Name** for the group and select **Create group**. The new group opens, empty. 3. Open each contact that belongs in it, tick the group under **Groups**, and select **Save changes**. A contact you add while a group is open starts in that group. With a group open, these buttons sit beside its name: - **Write to the group** opens a new message to every member that has an email address. - **Rename** changes the group's name. - **Delete group** removes the group after you confirm. Its contacts stay in the address book. ## Import contacts 1. Export your contacts from where they are now: - in Google Contacts, choose Export, then Google CSV or vCard; - in Outlook, export them as a CSV file; - in Apple Contacts, export them as a vCard. 2. On the **Contacts** page, select **Import**, then **Choose a file**, and choose the file. We add each contact whose email address is not in the address book yet, and skip the others. A contact with no email address is skipped when one with the same name is there already. The message then says how many came in, such as `Imported 12 contacts. 3 were already here or could not be read.` A label or category on an imported contact becomes a group of that name, or joins the group that has it already. A file can be up to 5 MB, and we read at most 5,000 contacts from one file. ## Export contacts 1. Select **Export**. 2. Choose **vCard file (.vcf)**, for phones and Apple Contacts, or **CSV file (.csv)**, for Google, Outlook and spreadsheets. The browser downloads `contacts.vcf` or `contacts.csv`. Both keep each contact's groups. ## Troubleshooting `Could not import the contacts: import up to 5 MB of contacts at a time.` : The file is larger than 5 MB. Split it into smaller files and import each one. A message that ends `no contacts found: import a CSV from Google or Outlook, or a vCard file.` : We could not read a contact in the file. Export the contacts again as a Google or Outlook CSV file or as a vCard file, and import that. A message that starts `Imported no new contacts.` : Every contact in the file was in the address book already, or could not be read. `No address book for this mailbox` : The mail server that holds this mailbox keeps no contacts, so there is nothing to show or import for it. `Enter a name, an email address or a phone number.` : A contact needs at least one of them. Fill one in, then save. `Enter an email address such as name@example.com.` : An address under **Email** is not complete. Correct it, or remove it with the cross beside it. A message that starts `You have a group called` : Another group has that name already. Choose another name. There is no **New contact** or **Import** button : You can only read this mailbox. Ask its owner to give you **Can read, organize and send**. ## Related - [Write and send email in webmail](/docs/mail/webmail/write-and-send/) - [Set up mail apps and devices](/docs/mail/webmail/mail-apps/) - [How webmail works](/docs/mail/webmail/) # Sort mail with labels and filters > Create labels, write filters that sort new mail as it arrives, and block senders you never want to hear from in webmail. Source: https://www.coritan.com/docs/mail/webmail/labels-and-filters/ In the dashboard: - /webmail/settings/labels: https://www.coritan.com/webmail/settings/labels - /webmail/settings/filters: https://www.coritan.com/webmail/settings/filters Labels group your conversations. They work like folders, but one conversation can have more than one label. Filters sort new mail as it arrives: they can move a message, label it, star it, forward it or delete it. Labels are on the **Labels** tab of webmail settings, at [www.coritan.com/webmail/settings/labels](https://www.coritan.com/webmail/settings/labels). Filters are on the **Filters and blocked** tab, at [www.coritan.com/webmail/settings/filters](https://www.coritan.com/webmail/settings/filters), which also blocks senders and imports and exports filters. Filters run on the mail server, so they sort mail while webmail is closed, and every app that reads the mailbox sees the result. Labels show in mail apps as folders. ## Before you begin - To create, rename or delete a label, you need to own the mailbox or be able to send from it. Someone who **Can only read** the mailbox sees its labels but cannot change them. - The **Filters and blocked** tab is for whoever manages the mailbox: its owner, or, for a mailbox that no Coritan account owns, everyone who can send from it. See [Who sees each tab](/docs/mail/webmail/settings/#who-sees-each-tab). ## Create a label 1. In the sidebar, under **Labels**, select **New label**. 2. Type a name of up to 200 characters, then press Enter. To stop, press Esc. The label appears in the sidebar. You can also create one on the **Labels** tab: type the name in **New label**, then select **Create label**. We show `Label created.` To open the **Labels** tab from the sidebar, select the gear beside **Labels** (**Manage labels**). ## Label a conversation - Open a conversation, or select some in the list, then select the tag button (**Labels**). Type in **Search labels** to narrow the list, then select a label to add it. A tick means the label is on every conversation you selected; select that label again to take it off. - Drag conversations from the list onto a label in the sidebar. This adds the label and leaves them where they are. - Select the folder button (**Move to**), then a label. The messages leave the folder you are in, and any other label they had, for that label alone. To take a label off an open conversation, select the X on the label's name above the first message. You can also press l to open the **Labels** menu, or v for **Move to**. Select a label in the sidebar to see its conversations. To search within one, see [Read and search mail in webmail](/docs/mail/webmail/read-and-search/). ## Rename or delete a label The **Labels** tab lists every label with the number of conversations it has. Select a label's name to open it. - To rename a label, select the pencil button beside it, change the name, then select **Save**. We show `Label renamed.` - To delete a label, select the bin button beside it, then confirm with **Delete label**. We show `Label deleted.` Deleting a label leaves its conversations in your mail. A conversation that was only under that label moves to Archive. > [!NOTE] > Webmail keeps the messages you schedule, the conversations you snooze and your templates in folders named Scheduled, Snoozed and Templates. They are not labels, so the **Labels** tab does not list them, but mail apps show them as folders. ## Create a filter A filter has conditions and actions. When a new message meets the conditions, the actions run on it. Filters run from the top of the list, and every filter a message matches acts on it, until one says **Stop here**. A filter acts only on mail that arrives after you save it. 1. On the **Filters and blocked** tab, select **Add filter…**. 2. In **Name**, type a name for the filter, such as `Invoices`. 3. Under **When a message matches**, choose **All conditions** or **Any condition**. 4. Fill in the first condition: what to look at, how to compare, and what to look for, such as **From**, **contains** and `billing@example.com`. Select **Add condition** for another one. 5. Under **Then**, choose the first action and, where it asks, a folder or an address. Select **Add action** for another one. 6. Select **Add filter**. We show `Filter “Invoices” added.`, and the filter appears at the bottom of the list with a summary of what it does. You can also start from mail you have: - From a message you are reading, open its menu (**Message actions**) and select **Filter messages like this**. The editor opens with the conditions **From** **contains** the sender's address and **Subject** **contains** the message's subject. - From a search, select **Show search options** in the search bar, fill in the fields, then select **Create filter**. The editor opens with a condition for each field you filled in. ### What a filter can look at | Look at | How it compares | | --- | --- | | **From**, **To**, **Cc**, **Subject**, **Message text** | **contains**, **does not contain** or **is** the text you type | | **Size** | **is larger than** or **is smaller than** a whole number of MB | | **Attachments** | **At least one** or **None** | ### What a filter can do | Action | What happens to the message | | --- | --- | | **Move to** | It goes to the folder or label you choose, and not to the Inbox. | | **Copy to** | It gets the label you choose, and it still arrives in the Inbox. | | **Mark as read** | It arrives marked as read. | | **Star it** | It arrives starred. | | **Mark it important** | It arrives marked as important. | | **Forward to** | It goes on to one confirmed address. A copy stays here unless you clear **Keep a copy in this mailbox**. See [Forward your mail to another address](/docs/mail/webmail/forwarding/). | | **Discard it** | It is deleted as it arrives and never reaches Trash. | | **Stop here** | The filters below this one do not run on it. | A filter files mail into Inbox, Archive, Spam, Trash or one of your labels. ## Change or turn off a filter The tab lists your filters in the order they run. On each row: - The switch at the start turns the filter off or on. A filter that is off reads **Off**, stays in the list and does nothing. - The arrows move the filter up or down the list. - The pencil button opens the filter. Change it, then select **Save filter**. - The bin button deletes the filter once you confirm with **Delete filter**. Mail the filter sorted already stays where it is. Blocked senders run before your filters, and so does forwarding all mail. When all mail is forwarded, the tab says so above the list, with a link to the **Forwarding** tab. ## Block a sender New messages from a blocked address go straight to Spam, even when the sender is one of your contacts. No other filter runs on them, so they are not forwarded either. 1. On the **Filters and blocked** tab, under **Blocked senders**, type an address such as `alex@example.com` in **Address or @domain**. To block everyone at a domain, type `@example.com`. 2. Select **Block**. We show `Mail from alex@example.com goes to Spam from now on.`, and the address joins the list. To block the sender of a message you are reading, open the message's menu (**Message actions**), select `Block` followed by the sender's name, then confirm with **Block**. Mail that arrived before you blocked the sender stays where it is. To unblock an address, select **Unblock** beside it in **Blocked senders**. ## Import and export filters - **Export** downloads `filters.json`, with your filters and blocked senders. Forwarding all mail is not in it. The button appears once you have a filter. - **Import…** reads a `filters.json` exported from this webmail, or the `mailFilters.xml` file that Gmail exports from its Filters and Blocked Addresses settings. Imported filters go after the ones you have. When you import, each label a filter uses is matched by name, and a label that does not exist yet is created. A filter keeps a forward only to an address that has confirmed on the **Forwarding** tab. Gmail filters that look for something this webmail cannot match, or that only do something it cannot do, such as sorting into categories, are left out. After the import, we say how many filters came in and what was left out. ## Filters written as Sieve A mailbox's filters are kept on the mail server as a script in the Sieve language. Webmail writes that script for you from your filters and blocked senders. You can also write it yourself. 1. On the **Filters and blocked** tab, select **Edit as Sieve**. The **Sieve script** box shows the script your filters make. 2. Change the script. To have the mail server check it, select **Validate script**. 3. Select **Save script**. We show `Sieve script saved.` To leave without saving, select **Back to rules**. If you changed the script, confirm with **Discard changes**. Once you save a script of your own, the tab shows the script instead of the list, and so it does when another app changes the script. Until the filters are rules again, you cannot add filters, block senders or forward all mail. To go back to rules, select **Replace with rules…** and confirm with **Replace script**. The script is deleted and you start again with no filters, so copy the script first if you want to keep it. ## Limits - A mailbox holds up to 100 filters. Forwarding all mail takes one, and blocked senders take one for each group of up to ten addresses. - A filter has up to 10 conditions and 10 actions, and forwards to one address. - What a condition looks for is up to 256 characters. A size is 1–1000 MB. - A filter's name is up to 120 characters, and a label's name up to 200. - A script is up to 100,000 characters. ## Troubleshooting A new filter did not sort the mail already in the mailbox : Filters act on mail as it arrives. To sort older mail, search for it, select the conversations, then use **Labels** or **Move to**. `Filters are not available on this mail server.` : The mail server that holds this mailbox runs no filters, so it cannot block senders or forward mail either. Labels still work. `The mail server is not running these filters at the moment, so new mail is not sorted by them.` : The filters are saved, but the mail server is not using them. Another app may have switched it to a script of its own. Save any change to the filters, such as turning one off and on again, to make them run again. `Give the filter a name.` : Type a name in **Name**. `Enter what to look for.` : A condition has an empty box. Type what to look for, or remove the condition with its X. `Enter a whole number of MB from 1 to 1000.` : A **Size** condition takes whole megabytes, such as `10`. `Choose a folder.` : A **Move to** or **Copy to** action has no folder yet. Choose one. A message that starts `A filter forwards to one address.` : Keep one **Forward to** action. To forward to two addresses, make a second filter with the same conditions. A message that ends `has not confirmed forwarding from this mailbox yet. Confirm it on the Forwarding tab first.` : The filter forwards to an address that has not confirmed. [Add and confirm the address](/docs/mail/webmail/forwarding/#add-and-confirm-a-forwarding-address), then save the filter again. `That folder no longer exists. Choose another.` or `The folder this filter files into no longer exists. Choose another.` : Someone deleted the label the filter files mail into. In the list, the filter's summary reads `a folder that no longer exists`. Edit the filter and choose another folder. A message that says another filter `files into a folder that no longer exists, so no change to the filters can be saved` : No change saves while a filter that is on files into a deleted label. Edit the filter the message names, or turn it off, then try again. `The mail server did not accept the filter` or `The script has an error` : The mail server could not read the filter or the script. The message says what it found. Fix the script, or replace it with rules. A warning that starts `These filters are a Sieve script, so blocking waits until they are rules again.` : You tried to block a sender while the filters are a script. Add the block to the script, or [replace the script with rules](#filters-written-as-sieve). `This mailbox has no Spam folder, so it cannot block anyone.` : Blocked mail goes to Spam, and this mailbox has none. A filter with **From** **contains** the address can still move the sender's mail elsewhere or discard it. `There is a label with that name already.` : Two labels cannot share a name. Choose another name, or use the label you have. A message that ends `you can read this mailbox but not change it.` : The mailbox was shared with you so that you can only read it. Ask its owner to let you send from it. There is no **Labels** tab or no **Filters and blocked** tab : **Labels** is for everyone who can send from the mailbox. **Filters and blocked** is for whoever manages it. Ask the mailbox's owner to make the change. ## Related - [Forward your mail to another address](/docs/mail/webmail/forwarding/) - [Read and search mail in webmail](/docs/mail/webmail/read-and-search/) - [Change webmail settings](/docs/mail/webmail/settings/) - [Open and share mailboxes in webmail](/docs/mail/webmail/mailboxes-and-sharing/) # Forward your mail to another address > Confirm an address with a code, then send all new mail on to it from webmail, or forward only some of it with a filter. Source: https://www.coritan.com/docs/mail/webmail/forwarding/ In the dashboard: - /webmail/settings/forwarding: https://www.coritan.com/webmail/settings/forwarding The **Forwarding** tab of webmail settings sends every new message the mailbox receives on to another address as well. It is at [www.coritan.com/webmail/settings/forwarding](https://www.coritan.com/webmail/settings/forwarding) for the mailbox on screen. To forward only some messages, a filter does it instead. Either way, the address has to confirm first. We send it a code from the mailbox, and nothing goes there until someone who reads it enters the code in webmail. Forwarding runs on the mail server as mail arrives, so it works with webmail closed. Forwarded mail leaves from this mailbox's own address. ## Before you begin - Open the mailbox as someone who manages it: its owner, or, for a mailbox that no Coritan account owns, someone who can send from it. Nobody else sees the tab ([Who sees each tab](/docs/mail/webmail/settings/#who-sees-each-tab)). - Be able to read the mail of the address you forward to, so you can copy the code. ## Add and confirm a forwarding address 1. On the **Forwarding** tab, under **Forwarding addresses**, enter the address in **Address**, such as `alex@example.com`. 2. Select **Send a code**. We show `We sent a code to alex@example.com. Enter it here once it arrives.` 3. In the other mailbox, open the message `Confirm forwarding from` followed by your mailbox's address, and copy the 8-digit code in it. 4. Back in webmail, type the code in the address's row and select **Confirm**. The row now reads **Confirmed**. Until then it reads **Waiting for the code we sent**, and nothing is forwarded there. - The code works for two days, and for 5 tries. - **Send again** sends a new code, and the one before stops working. - A mailbox can have up to 5 forwarding addresses and can send up to 10 codes a day. - The message with the code also shows in this mailbox's **Sent** folder, because the mailbox sends it. ## Forward all mail 1. On the **Forwarding** tab, under **Forward all mail**, choose `To` followed by a confirmed address in **Forward new mail**. 2. In **This mailbox's copy**, choose what happens to the message here: - **Keep it in the inbox** leaves it where it arrives; - **Keep it, marked as read** leaves it there, marked as read; - **Archive it** moves it to Archive; - **Move it to Trash** moves it to Trash. 3. Select **Save changes**. We show `New mail goes to alex@example.com as well from now on.`, and the address's row reads **Confirmed. All mail goes here.** Forwarding applies to mail that arrives from now on. Messages from [blocked senders](/docs/mail/webmail/labels-and-filters/#block-a-sender) go to Spam and are not forwarded. Your own filters still run on this mailbox's copy. ## Forward some mail with a filter A filter can forward the messages that match it to one confirmed address. Add the **Forward to** action to the filter and type the address; the field suggests your confirmed addresses. The filter keeps a copy in this mailbox unless you clear **Keep a copy in this mailbox**. See [Create a filter](/docs/mail/webmail/labels-and-filters/#create-a-filter). ## Stop forwarding Choose **Off** in **Forward new mail**, then select **Save changes**. We show `Forwarding all mail is off.` The address stays confirmed, so you can turn forwarding on again without a new code. A filter that forwards stops when you turn it off or delete it on the **Filters and blocked** tab. ## Remove a forwarding address 1. Select the bin button at the end of the address's row. 2. Confirm with **Remove address**. We show `alex@example.com removed.` Removing an address also takes it out of every filter that forwards there, and a filter that did nothing else is deleted. When all mail goes to the address, removing it turns forwarding all mail off as well. ## Troubleshooting `A mailbox does not forward to itself.` : You entered an address that this mailbox sends as. Enter an address somewhere else. `Enter an email address such as name@example.com.` : Enter a whole address, such as `alex@example.com`. A message that ends `is on the list already.` : The address is on the list. Enter its code, or select **Send again** for a new one. `This mailbox has 5 addresses. Remove one to add another.` : [Remove an address](#remove-a-forwarding-address) you no longer use, then add the new one. No code arrives : Look in the other mailbox's spam folder for the message `Confirm forwarding from` followed by your address. If it is not there, select **Send again**. `Enter the 8 digits of the code.` : The code has 8 digits. Copy it again from the message. `That code is not the one we sent.` : Type the code from the newest message. Each new code replaces the one before. `That code no longer works; send a new one.` : Two days have passed, or the code was typed wrong 5 times. Select **Send again**, then enter the new code. A message that ends `has not confirmed forwarding from this mailbox yet.` : A filter or **Forward new mail** names an address that has not confirmed. [Add and confirm](#add-and-confirm-a-forwarding-address) the address, then save again. `The filters are a Sieve script, so forwarding all mail waits until they are rules again.` : The mailbox's filters are a script written by hand, and forwarding all mail works only with rules. Select **Open the Filters tab**, then change the script or replace it with rules, as [Filters written as Sieve](/docs/mail/webmail/labels-and-filters/#filters-written-as-sieve) explains. A message that ends `The filters are a Sieve script that forwards there; change the script first.` : The script forwards to the address you want to remove. Take the address out of the script, then remove it. `Could not save forwarding: a filter files into a folder that no longer exists. Change that filter on the Filters tab first.` : A filter moves mail into a folder that was deleted. Edit that filter on the **Filters and blocked** tab to choose another folder, then save again. `Forwarding all mail works through filters, which this mail server does not run.` : The mail server that holds this mailbox runs no filters, so it cannot forward mail. A message that starts `Could not send a code to` and says there were too many requests : The mailbox has sent 10 codes in the last 24 hours. Wait, then select **Send again**. There is no **Forwarding** tab : Only whoever manages the mailbox sees it. For a mailbox shared with you, ask its owner. ## Related - [Sort mail with labels and filters](/docs/mail/webmail/labels-and-filters/) - [Send an automatic reply while you are away](/docs/mail/webmail/out-of-office/) - [Forward addresses with aliases](/docs/mail/mail-hosting/aliases/) - [Change webmail settings](/docs/mail/webmail/settings/) # Send an automatic reply while you are away > Turn on an out-of-office reply in webmail, choose the days it runs, and turn it off from your inbox when you are back. Source: https://www.coritan.com/docs/mail/webmail/out-of-office/ In the dashboard: - /webmail/settings/vacation: https://www.coritan.com/webmail/settings/vacation While an automatic reply is on, the mailbox answers each person who writes to it with a message you set, such as the day you are back. You set it on the **Out of office** tab of webmail settings, at [www.coritan.com/webmail/settings/vacation](https://www.coritan.com/webmail/settings/vacation) for the mailbox on screen. The mail server sends the reply, so it goes out with webmail closed. ## Before you begin - Open the mailbox in webmail as someone who manages it: its owner, or, for a mailbox that no Coritan account owns, someone who can send from it. Nobody else sees the tab ([Who sees each tab](/docs/mail/webmail/settings/#who-sees-each-tab)). - Know the days you are away, if the reply should start or stop on its own. ## Turn on an automatic reply 1. Go to **Settings**, then the **Out of office** tab. 2. Turn on **Send an automatic reply**. 3. Enter a **Subject** and a **Message**, such as `I am away until 14 October and will reply when I am back.` 4. To start or stop the reply on its own, choose a **First day**, a **Last day**, or both. 5. Select **Save**. We show `The automatic reply is on.` When the first day is still to come, we show `The automatic reply starts on` followed by that day. The days run from the start of the first day to the end of the last day, in UTC. Without a first day, the reply starts at once. Without a last day, it goes on until you turn it off. While the reply answers mail, a bar above your conversations says `Your automatic reply is on until` followed by the last day, or `Your automatic reply is on.` when it has none. Its **Settings** button opens the **Out of office** tab. ## Turn off the automatic reply - In the bar above your conversations, select **Turn off**. - Or, on the **Out of office** tab, turn off **Send an automatic reply** and select **Save**. We show `The automatic reply is off.` The subject, message and days stay on the tab, so next time you only turn it back on. After its last day, the reply stops on its own and the bar goes away. The switch on the tab stays on until you turn it off. ## Troubleshooting There is no **Out of office** tab : Only whoever manages the mailbox sees it. For a mailbox shared with you, ask its owner to set the reply. `The last day is before the first.` : Choose a **Last day** on or after the **First day**. `Automatic replies are not available for this mailbox.` : The mail server that holds this mailbox cannot send automatic replies for it at the moment. Reload the page later. If it stays, [contact support](/docs/support/). A message that starts `Could not save the automatic reply` : The mail server did not take the change, and the message says why. Try again in a moment. The reply started or stopped a few hours from midnight : The days count in UTC. For a reply that must start before you leave, choose a first day one day earlier. ## Related - [Change webmail settings](/docs/mail/webmail/settings/) - [Forward your mail to another address](/docs/mail/webmail/forwarding/) - [Sort mail with labels and filters](/docs/mail/webmail/labels-and-filters/) # Open and share mailboxes in webmail > See the mailboxes webmail opens, add one to your Coritan account, accept an invitation, and share a mailbox with other people. Source: https://www.coritan.com/docs/mail/webmail/mailboxes-and-sharing/ In the dashboard: - /webmail/settings/accounts: https://www.coritan.com/webmail/settings/accounts Webmail shows every mailbox this browser can open in one list. That is every mailbox your Coritan account opens (your own, the ones you added and the ones shared with you), and any mailbox signed in on this browser with its own password. The account menu at the top right lists them, and the **Mailboxes** tab of settings manages your account's list and who else can open your mailboxes. Each mailbox keeps its own folders, labels and settings. ## Before you begin - The **Mailboxes** tab needs your Coritan account. Sign in on coritan.com, or sign in to webmail with your coritan.gg address and your Coritan password ([Sign in to webmail](/docs/mail/webmail/sign-in/)). - To add a mailbox to your account, have its address and password, and its two-factor code if it asks for one. - To share a mailbox, you must own it and know your Coritan password. The other person needs a Coritan account. ## What each mailbox is The account menu and the **Mailboxes** tab describe each mailbox in the same words: **Your mailbox** : The mailbox you took with your account. It stays with your account, and you cannot remove it. See [Get a coritan.gg mailbox](/docs/mail/webmail/claim-a-mailbox/). **Added to your account** : A mailbox you added with its password, such as one on your Mail Hosting service. Your account owns it in webmail. **Given to you** : A mailbox an admin gave to your account. Only an admin can take it back. **Shared with you** : A mailbox its owner shared with you to read, organise and send from. **View only** : A mailbox shared with you to read, and nothing more. **Staff mailbox** : A mailbox shared with you as a member of Coritan staff, through your [staff link](/docs/account/staff-link/). It opens only while your Coritan account has two-factor authentication on. **Only on this browser** : A mailbox signed in here with its own address and password. Your Coritan account does not list it, so it does not open anywhere else. On the **Mailboxes** tab, a mailbox that is turned off reads `Turned off by whoever manages it`, and one we cannot reach at the moment reads `Cannot be reached right now`. The account menu says `Disabled, so it cannot be opened` and `Not reachable right now`. Neither opens until that changes. To open another mailbox, select it in the account menu, or select **Open** beside it on the **Mailboxes** tab. [Switch between mailboxes](/docs/mail/webmail/sign-in/#switch-between-mailboxes) says what happens to the message you are writing. ## Add a mailbox to your account A mailbox you add to your account opens wherever you sign in to Coritan. You can add a mailbox that Coritan hosts on this site and that no other Coritan account owns, such as one on your Mail Hosting service. 1. Go to **Settings**, then the **Mailboxes** tab. 2. Select **Add a mailbox to your account**. For a mailbox signed in on this browser already, select **Add to your account…** beside it instead. 3. Enter the mailbox's **Email address** and **Password**, then select **Add mailbox**. 4. If the mailbox has two-factor sign-in on, enter the code from its authenticator app in **Two-factor code**, then select **Add mailbox** again. A message says `alex@example.com is in your mailboxes now.`, and the list shows the mailbox as **Added to your account**. We check the password once and do not keep it. To read a mailbox on this browser only, without adding it to your account, use **Add another mailbox** in the account menu instead ([Add another mailbox](/docs/mail/webmail/sign-in/#add-another-mailbox)). ## Accept or decline an invitation When someone shares a mailbox with you, it waits for your answer. The account menu shows **An invitation is waiting**. 1. Select **An invitation is waiting** in the account menu, or go to the **Mailboxes** tab. 2. Under **Invitations**, read who shared the mailbox and the access they gave you. 3. Select **Accept** to add the mailbox to your list, or **Decline** to turn it down. If your account has no mailbox yet, [Webmail](https://www.coritan.com/webmail) lists the invitations on its first page, each with **Accept** and **Decline**. ## Share a mailbox Only the owner of a mailbox can share it. The people you share it with see it in their own webmail beside their mailboxes. Your password, two-factor authentication and app passwords stay yours. 1. Open the mailbox you want to share, then go to **Settings** and the **Mailboxes** tab. 2. Under **Sharing**, in **Invite someone**, enter the email the other person signs in to Coritan with. 3. In **Access**, choose what they can do: - **Can read, organize and send**: they read, organise and send as the mailbox, and can set up their own mail apps for it. - **Can only read**: they read the mail and change nothing. 4. Enter your Coritan password in the field under them. 5. Select **Share**. A message says `We invited alex@example.com.` The person's row reads **Invited, has not accepted yet** until they accept, then **Has access**. Sharing again with someone already on the list changes their access to what you chose. A mailbox can be shared with up to 50 people, and you can send up to 20 invitations an hour. People you share with keep their own view settings, such as **Density** and **Undo send**. The mailbox's name, signatures, filters, forwarding and automatic reply stay yours to change ([Who sees each tab](/docs/mail/webmail/settings/#who-sees-each-tab)). ## Change or end someone's access 1. Open the mailbox, then go to the **Mailboxes** tab. 2. Under **Sharing**, find the person. 3. Choose another access in the list on their row. To end their access, select the bin button on the row, then **Remove access**, or **Withdraw invitation** for someone who has not accepted yet. The change takes effect at once. When you remove someone, the devices they set up for the mailbox on the **Mail apps** tab stop signing in too. Access given by an admin shows **Given by an admin** and has no controls, because only an admin can change it. ## Remove or leave a mailbox 1. Go to the **Mailboxes** tab. 2. Beside the mailbox, select **Remove…** for one you added, or **Leave…** for one shared with you. 3. Confirm with **Remove mailbox** or **Leave mailbox**. A message says `alex@example.com is no longer in your mailboxes.` A mailbox you remove stays as it is, mail included, and you can add it again with its password. After you leave one, its owner can invite you again. Your own mailbox, a staff mailbox, and a mailbox or access an admin gave you have neither button. If the mailbox is on screen, webmail opens another one first. ## Troubleshooting `That address and password do not match a mailbox you can add.` : The address or the password is wrong, or the mailbox is not one that can be added to a Coritan account. Check both and try again. `That two-factor code is not right.` : Enter the code the mailbox's authenticator app shows now. Codes change every 30 seconds. `This mailbox belongs to another Coritan account. Ask its owner to share it with you.` : Another account owns the mailbox. Its owner can share it with you from their **Mailboxes** tab. `No Coritan account uses that email address.` : The email you entered under **Invite someone** is not the sign-in email of a Coritan account. Ask the person which email they sign in with, or ask them to [create an account](/docs/get-started/create-an-account/) first. `Incorrect account password.` : Enter the password you sign in to your Coritan account with, not the mailbox's password. `A mailbox can be shared with at most 50 people.` : Remove someone who no longer needs access, then share again. A mailbox you added to your account left your list : Someone changed or reset its password other than through your Coritan account: from its Mail Hosting service, or on the **Security** tab while signed in with the mailbox's own password. The people you had shared it with lost their access too. Add the mailbox again with its new password, then share it again. `Turn on two-factor for your Coritan account to open it` : The mailbox is a staff mailbox. Select **Turn on two-factor** beside it, and turn on [two-factor authentication](/docs/account/two-factor-authentication/#turn-it-on) for your account. The mailbox then opens. `Open another mailbox first, then remove alex@example.com.` : The mailbox is on screen and webmail has no other mailbox to open. Add or open another mailbox, then try again. A message that says there were too many requests : Your account can add up to 10 mailboxes an hour and send up to 20 invitations an hour. Wait, then try again. ## Related - [How webmail works](/docs/mail/webmail/) - [Sign in to webmail](/docs/mail/webmail/sign-in/) - [Set up mail apps and devices](/docs/mail/webmail/mail-apps/) - [Protect your webmail mailbox](/docs/mail/webmail/security/) # Protect your webmail mailbox > Change a mailbox's password, turn two-factor authentication on or off, manage app passwords and end sessions from the Security tab in webmail. Source: https://www.coritan.com/docs/mail/webmail/security/ In the dashboard: - /webmail/settings/security: https://www.coritan.com/webmail/settings/security The **Security** tab of webmail settings controls how people and mail apps sign in to a mailbox: its password, its two-factor authentication and its app passwords. For a mailbox you signed in to with its address, it also lists the browsers signed in to it. Open it from **Settings**, or go to [Security settings](https://www.coritan.com/webmail/settings/security) for the mailbox on screen. Everything on the tab belongs to the mailbox. Your Coritan account has a password and two-factor authentication of its own, and the tab links to them when the mailbox opened through your account. For those, see [Turn on two-factor authentication](/docs/account/two-factor-authentication/). ## Before you begin - Open the mailbox as its owner. The tab shows for your own coritan.gg mailbox, a mailbox you added to your account or an admin gave you, and any mailbox you signed in to with its own address and password. It does not show for a mailbox shared with you. - Know the password the tab asks for: - When the mailbox opened through your Coritan account, the tab asks for `Your Coritan password`. That covers being signed in on coritan.com, and a mailbox that your coritan.gg sign-in opened beside its own. - When you signed in with this mailbox's own address, the tab asks for its **Current password**. For your coritan.gg mailbox, that is your Coritan password until you give the mailbox one of its own. - For two-factor authentication, have an authenticator app on a phone or computer. ## Change the mailbox password The section is called **Mailbox password** when the mailbox opened through your Coritan account, and **Password** when you signed in with its address. 1. On the **Security** tab, enter `Your Coritan password` or the **Current password**, whichever the form asks for. 2. Enter the new password in **New password**, and again in **Confirm the new password**. It must be 12–128 characters and differ from the current one. 3. If two-factor authentication is on, you can tick **Also turn off two-factor sign-in** to turn it off at the same time. 4. Select **Change password**. We show `Password changed, and every other session signed out. Mail apps that sign in with the password need the new one.` From then on: - enter the new password in each mail app that used the old one; - app passwords keep working; - every browser signed in to the mailbox with its address is signed out, except this one if you signed in that way. The new password is for the mailbox alone. Your Coritan password stays as it is. For your coritan.gg mailbox, the new password becomes the mailbox's own. Mail apps and the mailbox's own sign-in use it from then on, and changing your Coritan password no longer changes it. Signing in to webmail with your coritan.gg address and your Coritan password still works, and still opens every mailbox your account can open. Through your Coritan account, the form never asks for the mailbox's old password. That makes it the way to set a new one when you have forgotten the mailbox's password. If you change the password of a mailbox you added to your account while signed in with the mailbox's own password, the mailbox leaves your account, and so does the access you gave other people. Change it through your Coritan account to keep both. ## Turn on two-factor authentication With two-factor authentication on, mail apps and the webmail sign-in form ask for a six-digit code from an authenticator app along with the mailbox's password. Opening the mailbox through your Coritan account never asks for it, because your account's own sign-in protects that. Signing in with your coritan.gg address and your Coritan password asks for your account's code instead, when your account has two-factor authentication on. Mail apps cannot ask for a code. Before you turn it on, give each mail app that signs in with the password an [app password](#create-an-app-password) of its own, or it stops getting mail. 1. On the **Security** tab, under **Two-factor sign-in**, turn on **Ask for a code when signing in**. 2. In **Add this mailbox to your authenticator**, select **Open in authenticator** on the device that has the app. Or select **Copy key** and type the key into the app. 3. Select **Done**. You will not see the key again, so keep a copy somewhere safe. Turning it on signs out every browser signed in to the mailbox with its address, except this one if you signed in that way. From then on, signing in with the mailbox's password shows **Enter your code** after it, as [Enter a code](/docs/mail/webmail/sign-in/#enter-a-code) describes. ## Turn off two-factor authentication 1. On the **Security** tab, turn off **Ask for a code when signing in**. 2. In **Turn off two-factor sign-in?**, enter `Your Coritan password`, or the mailbox's **Password** when you signed in with its address. 3. Select **Turn off two-factor**. We show `Two-factor sign-in is off.` Every browser signed in to the mailbox with its address is signed out, except this one if you signed in that way. When you signed in with the mailbox's address, its password stays as it is. Through your Coritan account, webmail sets the mailbox's password to your Coritan password as it turns two-factor authentication off: - for your coritan.gg mailbox, that is its password already; - for a mailbox you added or an admin gave you, your Coritan password becomes its password, so mail apps that used the old one need your Coritan password or an app password. Once a mailbox's password has been changed on this tab, the switch cannot turn two-factor authentication off through your Coritan account. [Change the password](#change-the-mailbox-password) with **Also turn off two-factor sign-in** ticked instead. ## Create an app password An app password signs one mail app in to the mailbox in place of its password, and it skips the two-factor code. Give each app its own, so you can revoke one without touching the others. 1. On the **Security** tab, under **App passwords**, type the app or device in **New app password for**, such as `Thunderbird`. It takes up to 64 characters. 2. Select **Create app password**. 3. Select **Copy password** and paste the password into the app. We show it once. 4. Select **Done**. In the mail app, use the mailbox's address as the username and the app password as the password. [Set up mail apps and devices](/docs/mail/webmail/mail-apps/) lists the server settings. The list also shows the passwords that webmail made for devices on the **Mail apps** tab, including those of the people you share the mailbox with. Their names end with the person's email in brackets. The mail server keeps at most ten app passwords for a mailbox. Webmail may use one itself, and each browser signed in with the mailbox's address and each device set up on the **Mail apps** tab uses one, so you may be able to create fewer. ## Revoke an app password 1. On the **Security** tab, find the password under **App passwords** by its name. 2. Select **Revoke…** on its row, then **Revoke app password**. We show `App password revoked.` The app or device that used it can no longer sign in, and gets no mail until you give it a new password. ## See and end sessions A *session* is one browser signed in to the mailbox with its address and password. The **Sessions** list shows every session of the mailbox, and it is on the tab when you signed in to this mailbox with its address. Through your Coritan account there are no sessions to list. Each row shows the browser and system, the IP address, when it signed in and when it was last used. The row for the browser you are using says **This device**. - To sign out another browser, select **Revoke…** on its row, then **Revoke session**. - To sign out every browser but this one, select **Sign out everywhere else…**, then **Sign out everywhere else**. - To sign out this browser, select **Sign out…** on its row, then **Sign out**. Ending a session does not change the password, so anyone who knows it can sign in again. To keep someone out, [change the password](#change-the-mailbox-password) as well. Sessions also end on their own, as [How long you stay signed in](/docs/mail/webmail/sign-in/#how-long-you-stay-signed-in) explains. To sign out of Coritan on other browsers, see [Sign out and end sessions](/docs/account/sessions/). Whoever manages a Mail Hosting service can also [end a mailbox's webmail sessions](/docs/mail/mail-hosting/mailbox-security/#end-webmail-sessions) from its panel. ## Troubleshooting There is no **Security** tab : Only the mailbox's owner sees it. For a mailbox shared with you, ask its owner to make the change. `That is not your Coritan password.` : Enter the password you sign in to Coritan with, not the mailbox's password. `That is not the current password.` or `That is not the password for this mailbox.` : Enter the mailbox's own password. If you signed in with your coritan.gg address and your Coritan password after giving the mailbox a password of its own, the form wants the mailbox's own. To use your Coritan password instead, sign in on coritan.com and open webmail from there. Wrong passwords here count towards the same limit as signing in to the address, which [Sign in to webmail](/docs/mail/webmail/sign-in/#troubleshooting) explains, and past it the tab says there were too many requests. `Use at least 12 characters.` : A new password must be 12–128 characters. `Choose a password different from the current one.` : Type a new password that is not the one you entered first. `The two new passwords do not match.` : Type the same new password in both fields. A message that starts `Could not turn it off: This mailbox has its own password` : The mailbox's password has been changed on this tab, so the switch cannot turn two-factor authentication off through your Coritan account. [Change the password](#change-the-mailbox-password) with **Also turn off two-factor sign-in** ticked. A mail app stopped signing in after two-factor authentication went on : The app signs in with the mailbox's password, and it cannot send a code. [Create an app password](#create-an-app-password) and enter it in the app in place of the password. A mail app stopped signing in after a password change : Enter the new password in the app, or give the app an app password. `Name the device or app it is for.` : Type a name in **New app password for** first. `Could not create the app password: that label is reserved.` : The names `webmail` and `webmail session` belong to webmail. Choose another name. A message that starts `Could not create the app password: mail engine error` : The mail server refused the new password. The mailbox may hold ten already. Revoke one you no longer use, or end a session, then try again. `The mail server did not return the password. Revoke this one and create another.` : Revoke the new app password in the list, then create another. You forgot the mailbox password : Through your Coritan account, the form asks for your Coritan password only. Sign in on coritan.com, open webmail and [change the password](#change-the-mailbox-password). For a Mail Hosting mailbox that is not in your account, whoever manages the service can [reset its password](/docs/mail/mail-hosting/mailboxes/#reset-a-mailbox-password). ## Related - [Sign in to webmail](/docs/mail/webmail/sign-in/) - [Set up mail apps and devices](/docs/mail/webmail/mail-apps/) - [Secure a mailbox](/docs/mail/mail-hosting/mailbox-security/) - [Turn on two-factor authentication](/docs/account/two-factor-authentication/) - [Sign out and end sessions](/docs/account/sessions/) # Set up mail apps and devices > Find the settings a mail app needs, set up an iPhone, iPad or Mac with a code, and remove a device from webmail. Source: https://www.coritan.com/docs/mail/webmail/mail-apps/ In the dashboard: - /webmail/settings/apps: https://www.coritan.com/webmail/settings/apps The **Mail apps** tab of webmail settings has what a phone or desktop mail app needs to open the mailbox: its server settings, and a code that sets up an iPhone, iPad or Mac with a password made for that device. It is at [www.coritan.com/webmail/settings/apps](https://www.coritan.com/webmail/settings/apps) for the mailbox on screen. Mail apps read mail over IMAP and send it over SMTP. ## Before you begin - Own the mailbox or be able to send from it. Someone who **Can only read** a mailbox has no **Mail apps** tab. - A staff mailbox opens in webmail only, so it cannot be set up in a mail app. - When two-factor authentication is on for the mailbox, a mail app cannot sign in with the mailbox's password, because it cannot send a code. Set the app up with a code, or give it an app password. ## Find the server settings The tab lists these settings. The copy button beside a setting copies it. For a server, it copies the name alone. **Email address** and **Username** : The mailbox's full address, such as `alex@coritan.gg`. **Password** : What the app signs in with. The row names it: - `Your Coritan password` for the mailbox your Coritan account came with, until it has a password of its own; - `The mailbox's own password` for every other mailbox, and for that one once you [change its password](/docs/mail/webmail/security/#change-the-mailbox-password). An app password works for any mailbox. The owner creates app passwords on the **Security** tab. **Incoming mail (IMAP)** : The server that apps read mail from, with port `993` and SSL/TLS. **Outgoing mail (SMTP)** : The server that apps send mail through, with port `587` and STARTTLS. **JMAP** : The address for an app that asks for a JMAP server. Most apps do not. **Storage** and **Largest attachment** : How much of its storage the mailbox uses, and the largest attachment webmail takes. Many mail apps look these settings up from your address alone. When an app asks for them, [set it up by hand](/docs/mail/mail-hosting/connect-a-mail-app/#set-up-an-app-by-hand) with the names from this tab. ## Set up an iPhone, iPad or Mac A code sets up the mailbox in Mail and its contacts in Contacts, with a password made for that device alone. It works for a mailbox shared with you too, so you never need the mailbox's password. 1. On the **Mail apps** tab, under **iPhone, iPad or Mac**, name the device in **Device**, or keep `iPhone`. A name can be up to 40 characters. 2. Select **Make a code**. A QR code appears. 3. Within 15 minutes, scan the code with the device's camera. On the device itself, you can select **Open the profile on this device** instead. The code works once. 4. Install the profile on the device. On an iPhone or iPad, the downloaded profile waits in the Settings app. 5. Select **Done** in webmail. The password is made when the device opens the code. Until then, the device's row reads **Waiting for its code to be opened**. Once the device has opened it, the row reads **In use**, with the date it was set up. ## Set up another mail app For Outlook, Thunderbird, Android and other apps: 1. Choose what the app signs in with. The owner can [create an app password](/docs/mail/webmail/security/#create-an-app-password) for it on the **Security** tab. Without two-factor authentication, the password the **Password** row names works too. 2. In the app, add an account with the [server settings](#find-the-server-settings) from the tab. Use the mailbox's address as the username. For a mailbox shared with you, webmail sets up only an iPhone, iPad or Mac. For another app, ask the mailbox's owner for an app password. ## Remove a device 1. On the **Mail apps** tab, find the device under **iPhone, iPad or Mac**. 2. Select the bin button at the end of its row, then confirm with **Remove device**. We show the device's name followed by `removed.` The device stops signing in to the mailbox at once. Each person sees only the devices they set up. The mailbox's owner also finds every device's password under **App passwords** on the **Security** tab, where a device that someone the mailbox is shared with set up shows their email address after its name. A device also stops signing in when: - the owner removes your access to a shared mailbox, or changes it to **Can only read**; - the mailbox passes to a new owner, for the devices the previous owner set up. ## Limits - Each person can have up to 20 devices on one mailbox. - Each person can make up to 10 codes an hour for one mailbox. - A mailbox holds at most ten app passwords, and each device's password is one of them. The mail server can refuse a new device before you reach 20. ## Troubleshooting **Its code was not opened in time** : Nobody opened the code within 15 minutes. Remove the row, then make a new code. `This link has been used or has expired; make a new one in the webmail` : Each code opens once, within 15 minutes. Make a new code. The device warns that the profile is not signed : We do not sign the profile. Check that it names your mailbox's address, then install it. `The mail server made no password; make a new link in the webmail` : The mailbox may already hold ten app passwords. Remove a device you no longer use, or ask the owner to revoke an app password on the **Security** tab. Then make a new code. `The mail server cannot be reached; try again in a moment` : Wait a minute, then open the code again. If 15 minutes have passed, make a new code. **Make a code** is greyed out : The **Device** field is empty, or you have 20 devices on this mailbox. Name the device, or remove one you no longer use. A message that ends `too many requests for this action. Please wait and try again.` : You made 10 codes for this mailbox within an hour. Wait, then try again. A message that ends `which checks two-factor each time; a device could not.` : This is a staff mailbox, which opens in webmail only. Read it in webmail. `Your devices could not be loaded.` : Reload the page. You can still make a code. The app says the username or password is wrong : Use the full address as the username. If two-factor authentication is on for the mailbox, the app needs an app password, or a code for an iPhone, iPad or Mac. For more causes, see [Connect a mail app to Mail Hosting](/docs/mail/mail-hosting/connect-a-mail-app/#troubleshooting). ## Related - [Protect your webmail mailbox](/docs/mail/webmail/security/) - [Connect a mail app to Mail Hosting](/docs/mail/mail-hosting/connect-a-mail-app/) - [Open and share mailboxes in webmail](/docs/mail/webmail/mailboxes-and-sharing/) # Import mail into webmail > Bring your old mail into a webmail mailbox from another provider or a file, and follow each import until it finishes. Source: https://www.coritan.com/docs/mail/webmail/import/ In the dashboard: - /webmail/settings/import: https://www.coritan.com/webmail/settings/import An *import* copies mail into the mailbox you have open, from another mail server or from a file. You start one on the **Import** tab of webmail settings, at [www.coritan.com/webmail/settings/import](https://www.coritan.com/webmail/settings/import). The import runs on our side, so it goes on after you close the page. Messages keep their folders, their read and flagged state and their own dates, and nothing changes on the other server. The same imports can be started from the Mail Hosting panel, as [Move existing mail into a mailbox](/docs/mail/mail-hosting/move-mail-in/) describes. ## Before you begin - Open the mailbox the mail should go into. With more than one mailbox, [switch to it](/docs/mail/webmail/sign-in/#switch-between-mailboxes) first. - Own the mailbox or be able to send from it. Someone who **Can only read** the mailbox has no **Import** tab. - Make sure the mailbox has room for the mail. An import pauses when the mailbox is full. The storage line at the foot of the sidebar shows how much is used. - For another server, have the old mailbox's address and password, and turn on IMAP there. Many providers want an *app password* made in their own settings, and the hint under **Provider** says what each one needs. - For a file, have the mail as an mbox file, a single `.eml` message, a zip or tar of mail or Maildir folders, or a cPanel backup. ## Open the Import tab In webmail, select **Settings** at the foot of the sidebar, then the **Import** tab. ## Import from another mail server 1. Under **From another mail server**, choose where the mail is now in **Provider**, such as `Yahoo Mail` or `iCloud Mail`. For a server the list does not name, choose `Another IMAP server` or `cPanel`, then enter the server's name in **Server**. 2. In **Username**, enter the old mailbox's sign-in name, usually its full address. In **Password**, enter its password or app password. 3. To copy the old Trash or Spam folders too, turn on **Also bring Trash** or **Also bring Spam**. 4. Select **Start the import**. We try the password first, so a wrong one fails straight away. When it works, we show `The import has started. It goes on with this tab closed.` and the import appears under **Imports**. The old server must offer IMAP over TLS on port `993`. We use the password for this import only, then forget it. Webmail brings all of the old mailbox's mail. To bring only the mail from some dates, start the import [with the API](/docs/mail/mail-hosting/move-mail-in/#with-the-api) instead. ## Import from Gmail or Microsoft 365 by signing in When **Provider** offers **Gmail: sign in with Google** or **Microsoft 365 or Outlook.com: sign in with Microsoft**, you can sign in on the provider's own page and give us no password. The access ends with the import. 1. In **Provider**, choose one of those two. 2. Optionally, turn on **Also bring Trash** or **Also bring Spam**. 3. Select `Continue with Google` or `Continue with Microsoft`, and sign in there. You come back to the **Import** tab, and we show `Signed in. The import has started, and goes on with this tab closed.` If the sign-in did not work, the message says why. To import from Gmail without signing in, choose `Gmail with an app password` and make an app password in your Google account. ## Import from a file 1. Under **From a file**, choose the file in **File**. 2. In **Put its mail in**, name the folder for mail that the file keeps in no folder, such as an mbox or an `.eml`. It is `Imported` by default. 3. For a cPanel backup, enter the mailbox you want in **Mailbox in a cPanel backup**. Leave it empty to import the mailbox with this mailbox's own address. 4. To copy the file's Trash or Spam folders too, turn on **Also bring its Trash** or **Also bring its Spam**. 5. Select **Upload and import**. A bar shows how much has uploaded. Keep the page open until the upload finishes. When the upload ends, we show the file's name followed by `is uploaded. Its mail is on its way in.` A file can be up to 2 GB by default. The note under **From a file** gives the limit that applies to your mailbox. [Move existing mail into a mailbox](/docs/mail/mail-hosting/move-mail-in/#import-from-a-file) lists the kinds of file we read and what we take from each. ## Follow an import Each import under **Imports** names its source, shows its status and counts its messages: how many came in, how many were already here, how many were left out and how many the mailbox refused. The list updates by itself every 5 seconds while an import is waiting or running. | Status | Meaning | | --- | --- | | **Waiting to start** | Queued to start. | | **Importing** | Copying mail now. | | **Paused** | Stopped for now. You paused it, the mailbox is full, or an error interrupted it. | | **Done** | Every message has been copied. | | **Stopped** | It failed. The row says why. | | **Cancelled** | You cancelled it. | We leave out messages marked deleted, messages larger than 50 MB and messages an earlier import already brought. [Where the mail goes](/docs/mail/mail-hosting/move-mail-in/#where-the-mail-goes) says which folder each old folder goes into. ## Pause, resume or cancel an import - To pause an import that is waiting or running, select **Pause** on its row. - To go on with a paused import, select **Resume**. It goes on from where it stopped. - To stop an import for good, select the X at the end of its row, then confirm with **Cancel import**. The mail that has come in already stays in the mailbox. Running the same import again is safe. Messages already copied count as already here and are not copied twice. ## Troubleshooting `3 imports are under way into this mailbox; wait for one to end` : A mailbox takes three imports at a time, counting waiting and paused ones. Wait for one to finish, or cancel one. `Too many requests for this action. Please wait and try again.` : A mailbox takes up to 20 import attempts a day, counting those the old server refused and those started from the Mail Hosting panel. Try again later. `The mailbox is full. Make room in it, then resume the import.` : An import stops when the mailbox reaches its quota. Delete mail you do not need, or ask whoever manages the mailbox for more room, then select **Resume**. For a Mail Hosting mailbox, see [Change a mailbox's quota](/docs/mail/mail-hosting/mailboxes/#change-a-mailbox-s-quota). `The import stopped on an error. Resume it to go on from where it was.` : Something interrupted the import, such as the old server closing the connection. Select **Resume**. `Could not load the imports` : Select **Try again**. If it keeps failing, reload the page. There is no **Import** tab : You can only read this mailbox. Ask its owner to change your access to **Can read, organize and send**. The old server refused the sign-in, or a file was not read : [Move existing mail into a mailbox](/docs/mail/mail-hosting/move-mail-in/#troubleshooting) explains each of those messages. ## Related - [Move existing mail into a mailbox](/docs/mail/mail-hosting/move-mail-in/) - [Import contacts](/docs/mail/webmail/contacts/#import-contacts) - [Change webmail settings](/docs/mail/webmail/settings/) # Change webmail settings > Set the name people see, your signatures, how webmail shows mail and your notifications, and find every other tab of webmail settings. Source: https://www.coritan.com/docs/mail/webmail/settings/ In the dashboard: - /webmail/settings/general: https://www.coritan.com/webmail/settings/general Webmail keeps the settings of the mailbox on screen in tabs. This page covers the **General** tab, where you set the name and signatures people see and how webmail shows mail to you. It also lists every tab, who sees it and the page that covers it. ## Before you begin - Open the mailbox in webmail. - The name, reply-to address and signatures of a mailbox belong to whoever manages it. Everyone else changes only their own view settings, as [Who sees each tab](#who-sees-each-tab) explains. ## Open settings Select **Settings** at the bottom of the folder list, the gear at the top right, or **Settings** in the account menu. Settings open on the **General** tab, at [www.coritan.com/webmail/settings/general](https://www.coritan.com/webmail/settings/general) for the mailbox on screen. Each tab has an address of its own under `/webmail/settings/`, so you can bookmark one. An address with a tab you do not see opens **General**. On a narrow screen, the tabs are one row that scrolls sideways. To go back to your mail, select the arrow beside the **Settings** heading. ## Who sees each tab Each person sees the tabs that their access to the mailbox allows. | Tab | Address | Who sees it | Covered in | | --- | --- | --- | --- | | **General** | `/webmail/settings/general` | Everyone | This page | | **Labels** | `/webmail/settings/labels` | Everyone who can send from the mailbox | [Sort mail with labels and filters](/docs/mail/webmail/labels-and-filters/) | | **Filters and blocked** | `/webmail/settings/filters` | Whoever manages the mailbox | [Sort mail with labels and filters](/docs/mail/webmail/labels-and-filters/) | | **Forwarding** | `/webmail/settings/forwarding` | Whoever manages the mailbox | [Forward your mail to another address](/docs/mail/webmail/forwarding/) | | **Out of office** | `/webmail/settings/vacation` | Whoever manages the mailbox | [Send an automatic reply while you are away](/docs/mail/webmail/out-of-office/) | | **Mailboxes** | `/webmail/settings/accounts` | Anyone using webmail through a Coritan account | [Open and share mailboxes in webmail](/docs/mail/webmail/mailboxes-and-sharing/) | | **Security** | `/webmail/settings/security` | The owner | [Protect your webmail mailbox](/docs/mail/webmail/security/) | | **Mail apps** | `/webmail/settings/apps` | Everyone who can send from the mailbox | [Set up mail apps and devices](/docs/mail/webmail/mail-apps/) | | **Import** | `/webmail/settings/import` | Everyone who can send from the mailbox | [Import mail into webmail](/docs/mail/webmail/import/) | *The owner* : You own your coritan.gg mailbox, a mailbox you added to your account, a mailbox an admin gave you, and any mailbox you signed in to with its own password. *Everyone who can send from the mailbox* : The owner, and the people it is shared with who **Can read, organize and send**. People who **Can only read** it see **General** and **Mailboxes**. *Whoever manages the mailbox* : Its owner. For a mailbox that no Coritan account owns, everyone who can send from it. *Anyone using webmail through a Coritan account* : You are signed in on coritan.com, or signed in to webmail with your coritan.gg address and your Coritan password. ## Set your name and reply-to address 1. On the **General** tab, under **Identities**, select **Edit…** beside the address. 2. In **Name**, enter the name that the people you write to see. 3. In **Reply-to**, enter the address that replies should go to, such as `alex@example.com`. Leave it empty to get replies at the address itself. 4. Select **Save changes**. We show `Identity saved.` The row then shows the name, and `Replies go to` followed by the address when you set one. The mailbox sends as its own address and as each [alias](/docs/mail/mail-hosting/aliases/) that delivers to it. When it has more than one, **Identities** lists them all, the mailbox's own address marked **Primary**, and each keeps its own name and reply-to address. You pick the address for a message in its **From** list, as [Write and send email in webmail](/docs/mail/webmail/write-and-send/) explains. People who do not manage the mailbox see the list without **Edit…**. ## Add a signature A mailbox keeps up to 20 signatures. Each address starts new mail with one of them, and replies and forwards with another. 1. On the **General** tab, under **Signatures**, select **New signature**. 2. In **Name**, name the signature, such as `Work`, then write it in **Signature**. 3. Under **Starts with**, choose a signature for each address: one for **New mail** and one for **Replies and forwards**. Choose **No signature** for none. 4. Select **Save signatures**. We show `Signatures saved.` To change a signature, select it in the list, edit it, then select **Save signatures**. To remove the one you have open, select **Delete this signature**, then **Save signatures**. As you write a message, its **Signature** menu swaps in another signature for that message only. People who do not manage the mailbox see which signatures it has but cannot change them. They pick one for each message from the **Signature** menu. ## Reading and sending These settings sit under **Reading and sending** on the **General** tab. **Undo send** : How long you have to take back a sent message: **Off**, **5 s**, **10 s**, **20 s** or **30 s**. It is 5 seconds unless you change it. See [Undo a send](/docs/mail/webmail/write-and-send/#undo-a-send). **Density** : **Comfortable** or **Compact**, which fits more conversations on the screen. **Comfortable** is the default. **Images in messages** : **Ask first** hides the pictures that a message loads from the web until you choose to show them, which keeps senders from seeing when you open it. **Always show** shows them straight away. **Ask first** is the default. **Desktop notifications** : Tells you of new mail while the webmail tab is in the background. It is off until you turn it on, and the browser asks for your permission the first time. **With the webmail closed** : Tells you of this mailbox's new mail on this browser even when no webmail tab is open. It shows only in a browser that supports it, and you turn it on in each browser for each mailbox. **Always show images from** : The senders whose images you chose to show every time. Select the cross beside a sender to ask first again. For a mailbox shared with you, **Undo send**, **Density** and **Desktop notifications** are your own, and the owner keeps theirs. **Images in messages** and **Always show images from** are the mailbox's, so only whoever manages it can change them. ## Troubleshooting `Could not load your settings` : Webmail could not read the mailbox's settings from the mail server. Select **Try again**. There is no **Edit…** button, or no way to change the signatures : You do not manage the mailbox. Ask its owner to make the change. `Enter an email address such as name@example.com.` : The **Reply-to** address is not complete. Enter a whole address, or leave the field empty. `This mailbox can no longer send as alex@example.com, so these changes cannot be saved.` : The alias behind that address no longer delivers to the mailbox. Whoever manages the Mail Hosting service can add it again. Select **Close**. `This signature is too long to save. Shorten it, or take out an image or some formatting.` : A signature holds about 20,000 characters, formatting included. Shorten it, then select **Save signatures** again. **New signature** is greyed out : The mailbox has 20 signatures, which is as many as it keeps. Delete one you no longer use, select **Save signatures**, then add the new one. `The browser is blocking notifications for this page. Allow them in its site settings, then turn this on again.` : Allow notifications for www.coritan.com in the browser's site settings, then turn **Desktop notifications** on again. **Images in messages** goes back to **Ask first** : The mailbox was shared with you, and only whoever manages it changes this setting. ## Related - [How webmail works](/docs/mail/webmail/) - [Write and send email in webmail](/docs/mail/webmail/write-and-send/) - [Send an automatic reply while you are away](/docs/mail/webmail/out-of-office/) - [Open and share mailboxes in webmail](/docs/mail/webmail/mailboxes-and-sharing/) # Webmail keyboard shortcuts > Every keyboard shortcut in webmail, for moving around, acting on conversations and writing mail. Source: https://www.coritan.com/docs/mail/webmail/keyboard-shortcuts/ In the dashboard: - /webmail/…: https://www.coritan.com/webmail Webmail takes the keys that most mail apps use. To see the list while you work, press ?, select the keyboard button at the top of the page, or choose **Keyboard shortcuts** in the account menu. ## Navigation | Keys | What it does | | --- | --- | | g then i | Go to Inbox | | g then s | Go to Starred | | g then t | Go to Sent | | g then d | Go to Drafts | | g then a | Go to All mail | | / | Put the cursor in the search box | | j or ↓ | In the list, move to the next conversation. While you read, j opens the next conversation in the list. | | k or ↑ | In the list, move to the previous conversation. While you read, k opens the previous conversation in the list. | | Enter or o | Open the conversation the list is on | | u | Go back from a conversation to the list | | n | While you read, open the next conversation in the list | | p | While you read, open the previous conversation in the list | | ? | Show or hide the list of shortcuts | | Esc | Close a dialog, such as the list of shortcuts, or clear the selection | The list shows the newest conversations first, so the next conversation is an older one. ## Actions An action key works on the conversations you have selected. With nothing selected, it works on the conversation the list is on, or on the one you are reading. | Keys | What it does | | --- | --- | | c | Open a new message | | r | While you read, reply to the newest message that is not a draft | | a | While you read, reply to everyone on that message | | f | While you read, forward that message | | e | Archive | | # | Move to Trash. In Trash or Spam, delete forever. | | ! | Report as spam | | s | Add a star, or remove it | | + or = | Mark as important | | - | Mark as not important | | b | Open the **Snooze until** menu | | m | Mute | | x | Select the conversation the list is on, or clear it | | Shift+i | Mark as read | | Shift+u | Mark as unread | | l | Open the **Labels** menu | | v | Open the **Move to** menu | | z | Undo the last action, while the bar at the bottom of the page shows **Undo** | In the list, b, l and v select the conversation the list is on when nothing is selected, then open the menu. When you press e, #, !, m or Shift+u while you read a conversation, webmail goes back to the list. In **Scheduled**, e, #, !, b and v do nothing. A message waiting there leaves only when you cancel its send, as [Change or cancel a scheduled message](/docs/mail/webmail/write-and-send/#change-or-cancel-a-scheduled-message) explains. ## Compose These keys work inside a compose window. On a Mac, press ⌘ in place of Ctrl. | Keys | What it does | | --- | --- | | Ctrl+Enter | Send the message | | Ctrl+B | Bold | | Ctrl+I | Italic | | Ctrl+U | Underline | | Ctrl+K | Insert a link | | Esc | Minimise the window, unless it fills the screen | In **To**, **Cc** or **Bcc**, Enter, a comma or a semicolon turns what you typed into a recipient. While suggestions show, Enter or Tab takes the highlighted one. See [Add recipients](/docs/mail/webmail/write-and-send/#add-recipients). ## When the keys work - The keys work only while the cursor is outside any field. While you type in the search box, a form or a message, they type as usual. - The keys in Navigation and Actions do nothing inside a compose window, while a dialog or a menu is open, or while Alt, Ctrl or ⌘ is held down. - For a pair such as g then i, press the second key within 1.2 seconds of the first. ## Related - [Read and search mail in webmail](/docs/mail/webmail/read-and-search/) - [Write and send email in webmail](/docs/mail/webmail/write-and-send/) - [How webmail works](/docs/mail/webmail/) # Mail DNS records > Every DNS record Mail Hosting and SMTP Relay ask for, what each one does, and when we publish them into your zone for you. Source: https://www.coritan.com/docs/mail/dns-records/ In the dashboard: - /dashboard/mail/…/domains: https://www.coritan.com/dashboard/mail Each domain on a mail service has its own list of records. To see it, open the service from [**Email**](https://www.coritan.com/dashboard/mail), go to the **Domains** tab and select the domain. The **DNS records** card lists every record with the exact value for that domain. This page explains each one. ## Records by purpose In the names and values below, `example.com` stands for your domain and `` for your service's mail host (see [Placeholders](#placeholders)). | Record | Type | Name | Priority | Value | Service | | --- | --- | --- | --- | --- | --- | | Ownership | TXT | `_mailverify.example.com` | | `mail-verify=` | Both, unless we publish for you | | Inbound mail | MX | `example.com` | `10` | `mx1.`, one record for each mail server listed | Mail Hosting | | Standby inbound mail | MX | `example.com` | `20` | A standby host, when the dashboard lists one | Mail Hosting | | SPF | TXT | `example.com` | | `v=spf1 include:_spf. -all` | Both | | DKIM | TXT | `._domainkey.example.com` | | `v=DKIM1; k=rsa; h=sha256; p=`, and a second record with `k=ed25519` | Both | | DMARC | TXT | `_dmarc.example.com` | | `v=DMARC1; p=none; adkim=r; aspf=r`, with the stage in `p=` | Both | | Mail app setup | CNAME | `autoconfig.example.com` | | `autoconfig.` | Mail Hosting | | Outlook setup | CNAME | `autodiscover.example.com` | | `autodiscover.` | Mail Hosting | | Outlook setup | SRV | `_autodiscover._tcp.example.com` | `0` | `0 443 autodiscover.` | Mail Hosting | | JMAP | SRV | `_jmap._tcp.example.com` | `0` | `1 443 jmap.` | Mail Hosting | | CardDAV | SRV | `_carddavs._tcp.example.com` | `0` | `1 443 jmap.` | Mail Hosting | | Sending from mail apps | SRV | `_submission._tcp.example.com` | `0` | `1 587 smtp.` | Mail Hosting | | Reading in mail apps | SRV | `_imaps._tcp.example.com` | `0` | `1 993 imap.` | Mail Hosting | | TLS reporting | TXT | `_smtp._tls.example.com` | | `v=TLSRPTv1;`, with a `rua=mailto:` report address when the service has one | Mail Hosting | | MTA-STS | CNAME | `mta-sts.example.com` | | `mta-sts.` | Mail Hosting | | MTA-STS | TXT | `_mta-sts.example.com` | | `v=STSv1; id=` | Mail Hosting | Publish every record the card lists. Only some of them decide whether the domain is verified (see [When a domain is verified](#when-a-domain-is-verified)); the rest let mail apps set themselves up and tell other mail servers to use TLS. An SMTP Relay domain gets only the ownership, SPF, DKIM and DMARC records. It gets no MX record, so the domain's inbox stays wherever it is now. Some domains list one more DKIM record with the note *Signs the mail this domain sends through our outbound relay.* Publish it as shown; its SPF record then also carries a second `include:`. ## Placeholders `example.com` : Your domain, exactly as you added it. A subdomain such as `send.example.com` gets records under its own name, such as `_dmarc.send.example.com`. `` : The mail host of your service's region, such as `mail-fra.coritan.com`. The **MX suffix** row on the service's **Overview** tab shows yours. `` : A random value we make for the domain when you add it. It never changes while the domain is on the service. `` : The name of a DKIM key, such as `cor202609r` for the RSA key and `cor202609e` for the Ed25519 key. Every domain has two keys, so it lists two DKIM records. `` : The public half of the DKIM key. Copy the whole value from the dashboard. `` : A label for the domain's MTA-STS policy. It changes when the policy does, and the dashboard shows the current one. ## What each check looks for We ask your domain's own nameservers first, so a record counts as soon as your DNS provider serves it. A problem the check finds shows in the **Last problem** row of the domain's page, under the domain's name on the **Domains** tab, and in the toast after **Verify now**. Ownership : A TXT record at `_mailverify.example.com` whose value is exactly `mail-verify=`. A domain whose records we publish into your zone passes without it. Keep the record in place: a later check that cannot find it sets the domain back to pending. MX : At least one MX record at the domain names one of the `mx` hosts the dashboard lists. When the domain has MX records and none of them is ours, the check reports `MX points to` followed by the hosts it found. SPF : A TXT record at the domain that starts with `v=spf1` and contains `_spf.`. Other mechanisms in the same record are fine. Keep one SPF record per name: add the `include:` to the record you have rather than publishing a second one. DKIM : The TXT record at each selector contains the key the dashboard shows. A missing or different key reports `DKIM selector not published`. Until the keys exist, the check reports `No DKIM keys yet: the mail engine has not made them`. DMARC : A TXT record at `_dmarc.example.com` that starts with `v=DMARC1`, whose `p=` value is the domain's current stage. A different value reports `DMARC publishes p=none, expected p=quarantine` (with the values found). See [DMARC](/docs/mail/dmarc/) for the stages. ## When a domain is verified | Service | Checks that must pass | Checks that are shown but not required | | --- | --- | --- | | Mail Hosting | Ownership, MX, SPF and DKIM | DMARC | | SMTP Relay | Ownership, SPF and DKIM | DMARC | A domain that has not passed is `pending`, and the **Domains** tab shows how many of its checks have passed, such as `Pending · 3/5`. We check pending domains again about every ten minutes. We do not re-check a verified domain on a timer; **Verify now** checks it on demand. Until ownership passes, the list has no DKIM records. We make the domain's keys once we find the ownership record, and the DKIM records then appear on the card. ## The DNS records card **Type** : The record type. **Name** : The full name to create. Some DNS providers want only the part before your domain, such as `_dmarc` for `_dmarc.example.com`. **Priority** : The priority of an MX or SRV record. **Value** : What the record holds. An SRV value is its weight, port and target, in that order. A note under a value explains a record that needs something from you. **Status** : The result of the last check for that record: **Verified** or **Missing**. Only the ownership, inbound MX, SPF, DKIM and DMARC records get one. The others, a retiring DKIM key and the extra DKIM record for the outbound relay have none. Name and value each have a copy button. ## DKIM key rotation We replace each verified domain's two DKIM keys every 90 days. | Stage | What happens | What the card shows | | --- | --- | --- | | New keys | We make two new keys. The old keys keep signing. | The new records, with the note *New DKIM key: publish now, the old one is retired after a week.* | | Switch | Once the check sees every new key in your DNS, the new keys sign instead. | The old records, with the note *Retiring DKIM key: keep it one more week, then delete it.* and no status | | Retire | Seven days after the switch, the old records leave the list. | Only the new records. Delete the old ones from your DNS. | When we publish into your zone, we add and delete these records ourselves. When your DNS is elsewhere, the switch waits until you publish the new keys. ## Records we publish for you When you add a domain whose DNS zone is on [Coritan DNS](/docs/websites/dns/manage-dns-records/) in the same account (the domain's own zone or a parent's), we write its records into the zone ourselves. Ownership passes at once, and the card reads *We publish these into your hosted zone and keep them up to date.* - Each record we write has a TTL of 300 seconds and the comment `coritan-mail`, followed by its purpose, in the zone's record list. - We update and delete only records with that comment. - **Verify now** writes any missing records again before it checks. - Removing the domain from the mail service deletes only our records. A record already in your zone always wins over ours: | Already in your zone | What we do | | --- | --- | | An SPF record at the domain | We add no SPF record. Add `include:_spf.` to yours, or the SPF check fails. | | A DMARC record at `_dmarc` | We add none. The domain's DMARC stage takes your record's `p=` value and is held there. Change `p=` in your record to move it. | | A TLS reporting or MTA-STS TXT record | We add none. | | A record at a DKIM selector's name, or at a name where we would add a CNAME or SRV record | We add none at that name. | | MX records that point somewhere else | We add no MX and no MTA-STS records. A Mail Hosting domain stays pending until you delete the old MX records. | ### Publish to your zone When the domain is inside a zone on this account's DNS but we are not writing into it (for example, you created the zone after adding the domain), the domain's page shows a **Publish to your zone** card. Its button, which shows the number of records, creates them through your DNS with a TTL of 3600 seconds. A record that is already there counts as present rather than as a failure. The card then reports how many records it created, how many were already present and how many it could not publish, with the reason for each. When all of them worked, it checks the domain a moment later. These records do not carry the `coritan-mail` comment, so we do not update them later: publish again after a DKIM rotation or a DMARC stage change. A name you claimed under [coritan.gg](/docs/proxies/coritan-gg-names/) needs no step at all: we publish its records and check the domain when you add it. ## Related - [Add a domain to Mail Hosting](/docs/mail/mail-hosting/add-a-domain/) - [Add a sending domain to SMTP Relay](/docs/mail/smtp-relay/add-a-sending-domain/) - [DMARC](/docs/mail/dmarc/) - [Manage DNS records](/docs/websites/dns/manage-dns-records/) - [Mail troubleshooting](/docs/mail/troubleshooting/) ## With the API Get a domain's records and its last check: ```bash curl https://api.coritan.com/api/v1/client/mail/4812/domains/311/records \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` Use `/client/smtp-relay/` in the path for an SMTP Relay service. The response holds the domain and the records to publish: ```json { "domain": { "id": 311, "domain": "example.com", "status": "pending", "verification_method": "txt", "checks": { "ownership": true, "mx": true, "spf": true, "dkim": false, "dmarc": true }, "dmarc_policy": "none", "dmarc_pinned": false, "last_checked_at": "2026-09-16T10:40:12Z", "last_error": "DKIM selector cor202609r not published" }, "records": [ { "type": "TXT", "name": "_mailverify.example.com", "content": "mail-verify=3kP0vS8x", "purpose": "ownership" }, { "type": "MX", "name": "example.com", "content": "mx1.mail-fra.coritan.com", "priority": 10, "purpose": "inbound" }, { "type": "TXT", "name": "example.com", "content": "v=spf1 include:_spf.mail-fra.coritan.com -all", "purpose": "spf" }, { "type": "TXT", "name": "cor202609r._domainkey.example.com", "content": "v=DKIM1; k=rsa; h=sha256; p=MIIBIjANBg...", "purpose": "dkim", "selector": "cor202609r" }, { "type": "SRV", "name": "_imaps._tcp.example.com", "content": "imap.mail-fra.coritan.com", "priority": 0, "weight": 1, "port": 993, "purpose": "autoconfig" } ] } ``` The example is shortened. Each record has these fields: `type`, `name`, `content` : The record to publish. `name` is always the full name. `priority`, `weight`, `port` : Present on MX and SRV records. `purpose` : What the record is for: `ownership`, `inbound`, `inbound_standby`, `spf`, `dkim`, `upstream_dkim`, `dmarc`, `autoconfig`, `autodiscover`, `jmap`, `carddav`, `tlsrpt` or `mta_sts`. `selector`, `state` : On DKIM records. `state` is `pending` for a new key to publish now and `retiring` for an old key to keep for one more week. `domain.checks` holds the result of the last check for ownership, MX, SPF, DKIM and DMARC. To run a check, see [Add a domain to Mail Hosting](/docs/mail/mail-hosting/add-a-domain/#with-the-api). A domain that is not on the service answers `404` `Domain not found`. ## API - `GET /api/v1/client/mail/{service_id}/domains/{domain_id}/records`: Domain records (https://www.coritan.com/docs/api/reference/client/mail/mail-domains/#op-get-api-v1-client-mail-service-id-domains-domain-id-records) - `GET /api/v1/client/smtp-relay/{service_id}/domains/{domain_id}/records`: Domain records (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-domains/#op-get-api-v1-client-smtp-relay-service-id-domains-domain-id-records) # How the DMARC stage ramp works > How we move a domain's DMARC policy from none to quarantine to reject, and how to hold a stage or change it yourself. Source: https://www.coritan.com/docs/mail/dmarc/ In the dashboard: - /dashboard/mail/…/settings: https://www.coritan.com/dashboard/mail - /dashboard/mail/…/domains: https://www.coritan.com/dashboard/mail DMARC tells other mail servers what to do with a message that claims to come from your domain but fails SPF and DKIM. Every domain on a Mail Hosting or SMTP Relay service has a DMARC record. It starts at the gentlest *stage*, and we move it up as reports show that your real mail passes. ## The three stages `p=none` : Receivers deliver failing mail as usual, and send reports about it. Every new domain starts here. `p=quarantine` : Receivers treat failing mail as suspicious, which often means the spam folder. `p=reject` : Receivers refuse failing mail. The stage is the `p=` value of the record at `_dmarc.example.com`, such as `v=DMARC1; p=quarantine; adkim=r; aspf=r`. The record uses *relaxed alignment* (`adkim=r; aspf=r`): a message passes when its SPF or DKIM domain shares its main domain with the From address, such as `send.example.com` for mail from `alex@example.com`. [Mail DNS records](/docs/mail/dns-records/) lists the full record and how we check it. ## When the stage moves up Once a day we look at every verified domain that is not held and not yet at `p=reject`. A domain moves up one stage when all of these are true: - It has been at its current stage for at least 14 days. - The aggregate reports from receivers over the last 14 days cover at least 100 messages. - At least 98% of those messages passed. - No receiver sent a failure report in that time. The ramp never moves a domain down. Moving down is always your choice, because a weaker policy also makes your domain easier to spoof. Receivers send their reports to the `rua=mailto:` address in the record, when the record the dashboard shows has one. Keep that part of the record as it is: without reports, the stage stays where it is. ## What changes when the stage moves When we publish your records into a zone on Coritan DNS, we replace our `_dmarc` record with the new value ourselves. When your DNS is elsewhere, the **DNS records** card shows the new value, and the domain's **DMARC** check fails with `DMARC publishes p=none, expected p=quarantine` until you update the record at your DNS provider. We send no message about it: the **DMARC** column on the **Domains** tab shows each domain's current stage. DMARC does not count towards verification, so the domain keeps sending while the record catches up. ## Holding a stage or setting one yourself Two places show a domain's stage and change it: - The **DMARC policy** card on the domain's page: go to the **Domains** tab and select the domain. It has the **Stage** select and the **Hold this stage** switch, and says since when the domain has been at its stage. - The **DMARC** card on the **Settings** tab, on Mail Hosting only. It lists every domain with a stage select and a **Hold stage** switch, and says for each whether it is held or ramps automatically. Choosing a stage applies it at once and holds the domain there, so the ramp stops. Turning the hold off lets the ramp carry on from the stage you chose. Holding a stage without changing it stops the ramp where it is. A change you make takes effect the same way as one the ramp makes: we update a record we publish, and you update a record you publish yourself. ## When your zone already has a DMARC record When we publish into your zone on Coritan DNS and the zone already has a DMARC record that we did not add, your record stays and ours is never added. The domain's stage takes your record's `p=` value and is held there, and the ramp does not change it. To move such a domain, change `p=` in your own record, then select **Verify now** on the domain's page so the stage follows it. Choosing a stage in the dashboard is refused with `The DMARC record for example.com in your DNS was not added by us. Change its p= value in the zone instead.` ## With the API Set a stage and hold it: ```bash curl -X PATCH https://api.coritan.com/api/v1/client/mail/4812/domains/311/dmarc \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"stage": "quarantine", "pinned": true}' ``` Use `/client/smtp-relay/` in the path for an SMTP Relay service. The body takes two optional fields: `stage` : `none`, `quarantine` or `reject`. Leave it out to keep the current stage. Any other value answers `422`. `pinned` : `true` holds the stage and `false` lets the ramp move it. Leave it out to keep the current setting. Unlike the dashboard, sending a `stage` alone does not hold it. The response holds the updated domain, with `dmarc_policy`, `dmarc_pinned` and `dmarc_stage_since`, and its records to publish, in the same shape as [the records endpoint](/docs/mail/dns-records/#with-the-api). A stage change refused because of your own DMARC record answers `409` with the message above, and a domain that is not on the service answers `404` `Domain not found`. ## API - `PATCH /api/v1/client/mail/{service_id}/domains/{domain_id}/dmarc`: Set DMARC (https://www.coritan.com/docs/api/reference/client/mail/mail-domains/#op-patch-api-v1-client-mail-service-id-domains-domain-id-dmarc) - `PATCH /api/v1/client/smtp-relay/{service_id}/domains/{domain_id}/dmarc`: Set DMARC (https://www.coritan.com/docs/api/reference/client/mail/smtp-relay-domains/#op-patch-api-v1-client-smtp-relay-service-id-domains-domain-id-dmarc) # How Mail Hosting and SMTP Relay are billed > What a mail plan includes, what you pay for, how to get a bigger or smaller plan, and what the Billing tab shows and cancels. Source: https://www.coritan.com/docs/mail/billing/ In the dashboard: - /dashboard/mail/…/billing: https://www.coritan.com/dashboard/mail Each Mail Hosting or SMTP Relay service is on one plan, and you pay that plan's price each billing cycle. The plan sets what the service may hold and send. Each service has a **Billing** tab, where you see the plan, follow its billing history and cancel it. ## What a plan includes The [Mail page](https://www.coritan.com/solutions/mail) and the order pages list the plans, what each includes and its price. Mail Hosting : A number of mailboxes, storage for each mailbox and a number of domains. The storage is one pool for the whole service ([What the plan sets](/docs/mail/mail-hosting/#what-the-plan-sets)). Aliases do not count against the plan. SMTP Relay : A number of emails a month, a buffer over that number on paid plans, and a number of sending domains. Plans of 250,000 emails a month and more include a dedicated sending address ([How SMTP Relay sending limits work](/docs/mail/smtp-relay/sending-limits/)). ## What you pay You pay the plan's price once each billing cycle. A Mail Hosting plan can have more than one billing cycle, and its order page offers the ones it has. Relay plans are billed monthly. Nothing on a mail service is charged by use. A relay's buffer costs nothing extra, and there is no charge for mail over the allowance: once the allowance and its buffer are used up, the relay refuses mail until the 1st ([What happens near the allowance](/docs/mail/smtp-relay/sending-limits/#what-happens-near-the-allowance)). The one extra charge is a dedicated IP add-on, which is a service of its own ([Dedicated IP add-ons](#dedicated-ip-add-ons)). You find and pay a mail service's invoices on the **Invoices** tab under **Billing**, as for any service ([Pay an invoice](/docs/billing/invoices/)). ## The Billing tab In the dashboard, go to [**Email**](https://www.coritan.com/dashboard/mail), open the service, then the **Billing** tab. It works from the moment you order, before the service is set up. The **Current plan** card shows: **Product** : The mail plan the service is on. **Plan** : The billing cycle you pay on. **Price** : What one billing cycle costs. **Status** : The service's status, such as `active` or `suspended`. **Next renewal** : The date the next invoice for the service is due. **Set up** : When the service was set up, or `Pending` until then. **Service ID** : The number to give support when you ask about the service. **Change plan…** in the card's header moves the service to another billing cycle of the same plan ([Change a service's plan](/docs/billing/change-plan/)). A plan with one billing cycle shows `No other plan to switch to` in its place, and so does every relay plan. The **Activity** table lists the service's billing history, such as its setup, renewals and plan changes. **By** says who or what made each change. Two alerts can appear above the card. **Suspended** means an invoice is unpaid ([When a payment is late](#when-a-payment-is-late)). **Cancellation scheduled** gives the date the service ends. The **Cancel service** card at the bottom ends the service ([Cancel a mail service](#cancel-a-mail-service)). ## Move to a bigger or smaller plan Each mail plan, such as a Mail Hosting plan with more mailboxes or a relay plan with more emails a month, is a product of its own in the catalogue. **Change plan…** moves a service only to another billing cycle of the same plan, so it cannot give a service more or less of anything. Only our staff can change what an existing service allows. [Contact support](/docs/support/conversations/) with the **Service ID** and the plan you want. Before a move to a smaller plan, delete the mailboxes, SMTP credentials and domains that are over the smaller plan's numbers. On Mail Hosting, also shrink mailbox quotas until they fit in its storage. A change that would leave the service holding more than the plan allows is refused. For a relay, you can instead order a relay on the plan you want, move your sending to it, and then cancel the old relay. The new relay starts with the starting hourly limit, however long the old one has been sending ([How the limit rises](/docs/mail/smtp-relay/sending-limits/#how-the-limit-rises)). A domain can be on one mail service at a time, and a relay removes a domain only after its SMTP credentials are deleted ([Remove a domain](/docs/mail/smtp-relay/add-a-sending-domain/#remove-a-domain)). So your applications cannot send from a domain while you move it. Giving the new relay a new sending domain, such as `mail.example.com`, avoids that gap. ## The free SMTP Relay plan Each account can hold one free relay. The free plan has no buffer and stops at its allowance ([The monthly allowance](/docs/mail/smtp-relay/sending-limits/#the-monthly-allowance)). Ordering it needs the verification check on its order page ([Order Mail Hosting or SMTP Relay](/docs/mail/order-a-mail-service/)). A free relay counts against that limit until it has ended, including one you have cancelled at the end of its term. While it counts, ordering another free relay fails with `You already have 1 free SMTP Relay. Upgrade or remove one to create another.` To send more than the free plan allows, order a paid relay. Paid relays do not count against the limit, so you can keep the free one or cancel it once your applications use the new relay. ## Dedicated IP add-ons A dedicated address you order from a relay's **Sending IPs** card is an *add-on*: a service of its own, with its own invoice and renewal date. The **Sending IPs** card and its order page show its price, and [Dedicated sending addresses](/docs/mail/smtp-relay/sending-limits/#dedicated-sending-addresses) explains what it does. The address that comes with a plan of 250,000 emails a month or more is part of the plan and has no service of its own. An add-on does not appear in the **Email** list. To reach its billing, select **Services** in the sidebar, then the add-on, which is listed as an SMTP Relay service. Its page opens on a tab that shows **Could not load this service**; select the **Billing** tab, which works as on any mail service. While an add-on's invoice is unpaid and the add-on is suspended, the relay stops sending from its address and the **Sending IPs** card shows **Disabled** beside it. The relay sends from its other addresses meanwhile. Paying the invoice turns the address back on. When you cancel an add-on and it ends, we release its address. > [!WARNING] > Cancelling a relay does not cancel its add-ons. When the relay ends, we release every address it holds, add-ons included, but each add-on keeps renewing and raising invoices until you cancel it. Cancel each add-on from its own **Billing** tab as well. ## When a payment is late When a mail service's invoice stays unpaid, we suspend the service ([Failed payments and suspended services](/docs/billing/failed-payments/)). The service's status becomes `suspended`, and its **Billing** tab shows **Suspended** with the reason, or `Pay the open invoice to restore the service.` While a service is suspended: - Its mailboxes cannot sign in to webmail or a mail app, and cannot send or receive mail. - The relay's SMTP credentials cannot sign in, and the send API answers `401` with `Invalid API key`. Nothing is deleted while the service is suspended. Pay the open invoice to restore it as it was. A service left unpaid is later cancelled, and its mail is then deleted. ## Cancel a mail service On the **Billing** tab, select **Cancel service…**. Under **When**, choose **At the end of the current term** to keep the service running until the date the dialog shows, or **Immediately** to end it now. Then type `cancel` and select **Cancel service**. [Cancel a service](/docs/billing/cancel-a-service/) describes each choice and what the dashboard shows afterwards. We do not refund unused time. When the service ends: - Its mailboxes, aliases and stored mail are deleted, and so are a relay's SMTP credentials. Its API keys stop working. - Every webmail session of its mailboxes ends. - Its domains stop working on it, and you can add them to another mail service. We delete the records we published in DNS zones you host with us. Records you added at another DNS provider stay there until you delete them ([Mail DNS records](/docs/mail/dns-records/)). - A relay's dedicated addresses are released, add-ons included. > [!CAUTION] > You cannot restore a mail service or its mail once it has ended. Copy out the mail you want to keep first, for example with a mail app over IMAP. ## Related - [Change a service's plan](/docs/billing/change-plan/) - [Cancel a service](/docs/billing/cancel-a-service/) - [How SMTP Relay sending limits work](/docs/mail/smtp-relay/sending-limits/) - [Request a higher hourly limit](/docs/mail/smtp-relay/request-a-higher-limit/) ## With the API A mail service is billed through the same endpoints as every service. [Change a service's plan](/docs/billing/change-plan/#with-the-api) previews and applies a switch to another billing cycle, and [Cancel a service](/docs/billing/cancel-a-service/#with-the-api) cancels it. Pass the service ID as `service_ref`. # Internal mail tenants > How the platform's own mail tenants appear on the owning account's dashboard, and what their owner and staff can each change. Source: https://www.coritan.com/docs/mail/internal-tenants/ In the dashboard: - /dashboard/mail/internal/…/overview: https://www.coritan.com/dashboard/mail/internal - /dashboard/mail/internal/…/domains: https://www.coritan.com/dashboard/mail/internal - /dashboard/mail/internal/…/mailboxes: https://www.coritan.com/dashboard/mail/internal - /dashboard/mail/internal/…/aliases: https://www.coritan.com/dashboard/mail/internal - /dashboard/mail/internal/…/settings: https://www.coritan.com/dashboard/mail/internal - /dashboard/mail/internal/…/credentials: https://www.coritan.com/dashboard/mail/internal - /dashboard/mail/internal/…/api-keys: https://www.coritan.com/dashboard/mail/internal - /dashboard/mail/internal/…/webhooks: https://www.coritan.com/dashboard/mail/internal - /dashboard/mail/internal/…/suppressions: https://www.coritan.com/dashboard/mail/internal - /dashboard/mail/internal/…/events: https://www.coritan.com/dashboard/mail/internal - /dashboard/mail/internal/…/send: https://www.coritan.com/dashboard/mail/internal An *internal mail tenant* is a Mail Hosting or SMTP Relay setup that belongs to the platform itself, such as the relay that sends the platform's own emails or the team mailboxes on its own domains. The platform's seed file declares each one. An internal tenant has no order and no bill, so it is not a service. Only the account that the platform's mail belongs to sees internal tenants. They appear on that account's **Email** page with its ordinary services. On any other account, a link to one opens the page-not-found screen. ## How an internal tenant appears In the **Email** list, internal tenants come first. The line under each one's domain gives the tenant's name followed by `internal, no billing`. Select the row to open the tenant. The tenant's page looks like a service's page, with these differences: - The header shows an **Internal** badge beside the status and the product, and the tenant's number, such as `Tenant #12`, where a service shows its service number. - A note below the header, **One of the platform's own tenants**, says that it has no order or bill, that staff set its limits, and that only staff can remove what the seed file declares. - A Mail Hosting tenant has the tabs **Overview**, **Domains**, **Mailboxes**, **Aliases** and **Settings**. An SMTP Relay tenant has **Overview**, **Domains**, **Credentials**, **API keys**, **Webhooks**, **Suppressions**, **Events** and **Send**. Neither has a **Billing** tab. - On the **Overview** tab, the **Tenant** card takes the place of the **Service** card. Its **Plan** reads `Internal, no billing`, it shows the **Tenant ID**, and it has no renewal date. The tab has no **Tags** card. - An SMTP Relay tenant's **Overview** tab has no **Sending IPs** card. When the monthly allowance runs out, its warning says `We refuse further emails until the next month starts or staff raise the allowance.` and has no **Change plan** button. **Open webmail** at the top of a Mail Hosting tenant opens webmail, as it does on a service. The **Sending is paused** and **Under review** notes appear on a tenant as they do on a service ([Sending reputation and deliverability](/docs/mail/smtp-relay/deliverability/)). ## What the owner can change The owning account uses each tab as it would on a service of the same kind, within the tenant's limits: - Add domains, publish and verify their records, and set their DMARC stage ([Add a domain to Mail Hosting](/docs/mail/mail-hosting/add-a-domain/), [Add a sending domain](/docs/mail/smtp-relay/add-a-sending-domain/)). - Create and manage mailboxes and aliases ([Create and manage mailboxes](/docs/mail/mail-hosting/mailboxes/), [Forward addresses with aliases](/docs/mail/mail-hosting/aliases/)). - Create credentials and send API keys, add webhooks, manage the suppression list, look up events and send a test message ([How SMTP Relay works](/docs/mail/smtp-relay/)). - Set the tenant's default message category: on the **Settings** tab of a Mail Hosting tenant, and in the **Deliverability** card on the **Overview** tab of an SMTP Relay tenant ([Transactional and marketing mail](/docs/mail/smtp-relay/message-categories/)). A mailbox that the platform created for a tenant starts with a password that nobody has seen. Set one with **Reset password…** in the mailbox's menu on the **Mailboxes** tab before anyone signs in ([Reset a mailbox password](/docs/mail/mail-hosting/mailboxes/#reset-a-mailbox-password)). The seed file can also name a domain without adding it, so that the owning account decides when the domain's mail moves. Add it on the **Domains** tab when you are ready ([Move a domain that already receives mail](/docs/mail/mail-hosting/add-a-domain/#move-a-domain-that-already-receives-mail)). We record every change the owning account makes to an internal tenant, from the dashboard or through the API, in the platform's audit log. ## What only staff can change Staff make these changes from the admin console: A domain that the seed file declares : Only staff can remove it. The domain's page on the **Domains** tab shows `The platform's seed file declares this domain, so only staff can remove it, from the admin console.` under **Remove this domain**, and greys out **Remove domain…**. The **Remove domain…** button in the **Danger zone** card on the **Settings** tab is not greyed out, but the removal fails with the same message. Once you add a domain that the seed file names, it is locked in the same way. The credential the platform sends its own mail with : On an SMTP Relay tenant, the credential's row on the **Credentials** tab shows `The platform sends its own mail with this credential. You can rotate it, but only staff can disable or delete it.` Its **Enabled** switch is greyed out, and **Revoke credential…** shows **Staff only**. **Rotate password…** works: the platform's own mail uses the new password from its next message ([Rotate a password](/docs/mail/smtp-relay/credentials/#rotate-a-password)). The tenant's limits : Staff set how many domains, mailboxes and credentials a tenant may hold, the storage for each mailbox, the monthly allowance and the hourly limit. On the **Hourly limit** card, **Request limit increase…** is greyed out, with the note `Staff set the limits of an internal tenant, from the admin console.` The limits the seed file declares apply again each time the platform starts. ## With the API The routes under `/api/v1/client/platform-mail/{tenant_id}` are the same as a mail service's under `/api/v1/client/mail/{service_id}` and `/api/v1/client/smtp-relay/{service_id}`. They take the tenant ID where a service's routes take the service ID, and each operation takes the same body and answers in the same shape as on a service. The pages linked above document each operation. As on a service, an operation that belongs to the other product answers with a message that says so, such as `Not an SMTP Relay service`. There is no list under `platform-mail`. `GET /api/v1/client/mail/services` lists internal tenants first, each with `"internal": true`, `"service_id": null` and its `tenant_id` ([Email](/docs/mail/#with-the-api)). `GET /api/v1/client/platform-mail/{tenant_id}` returns the tenant's summary, as the summary of a [Mail Hosting](/docs/mail/mail-hosting/#with-the-api) or [SMTP Relay](/docs/mail/smtp-relay/#with-the-api) service does. `kind` is `hosting` or `relay`, `label` is the tenant's name and `service` is `null`. Set the default message category with `PATCH /api/v1/client/platform-mail/{tenant_id}/category`: ```bash curl -X PATCH https://api.coritan.com/api/v1/client/platform-mail/12/category \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"default_category": "marketing"}' ``` ```json {"default_category": "marketing"} ``` `default_category` is `transactional` or `marketing`, and any other value answers `422`. In the answers to `GET /api/v1/client/platform-mail/{tenant_id}/domains` and `GET /api/v1/client/platform-mail/{tenant_id}/credentials`, each item carries `locked`. It holds the reason when only staff can remove the item, and `null` otherwise. | Request | Status | `detail` | | --- | --- | --- | | `DELETE …/domains/{domain_id}` for a domain the seed file declares | `409` | `The platform's seed file declares this domain, so only staff can remove it, from the admin console.` | | `PATCH …/credentials/{account_id}/enabled` with `"enabled": false`, or `DELETE …/credentials/{account_id}`, for the platform's sending credential | `409` | `The platform sends its own mail with this credential. You can rotate it, but only staff can disable or delete it.` | | `POST …/limits/increase-request` | `409` | `Staff set the limits of an internal tenant, from the admin console.` | | Any request with a tenant ID that is not an internal tenant of your account | `404` | `Mail service not found` | ## API - `GET /api/v1/client/platform-mail/{tenant_id}`: Get summary (https://www.coritan.com/docs/api/reference/client/mail/platform-mail/#op-get-api-v1-client-platform-mail-tenant-id) - `GET /api/v1/client/platform-mail/{tenant_id}/domains`: List domains (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-domains/#op-get-api-v1-client-platform-mail-tenant-id-domains) - `POST /api/v1/client/platform-mail/{tenant_id}/domains`: Add domain (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-domains/#op-post-api-v1-client-platform-mail-tenant-id-domains) - `DELETE /api/v1/client/platform-mail/{tenant_id}/domains/{domain_id}`: Remove domain (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-domains/#op-delete-api-v1-client-platform-mail-tenant-id-domains-domain-id) - `PATCH /api/v1/client/platform-mail/{tenant_id}/domains/{domain_id}/dmarc`: Set DMARC (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-domains/#op-patch-api-v1-client-platform-mail-tenant-id-domains-domain-id-dmarc) - `GET /api/v1/client/platform-mail/{tenant_id}/domains/{domain_id}/records`: Domain records (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-domains/#op-get-api-v1-client-platform-mail-tenant-id-domains-domain-id-records) - `POST /api/v1/client/platform-mail/{tenant_id}/domains/{domain_id}/verify`: Verify domain (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-domains/#op-post-api-v1-client-platform-mail-tenant-id-domains-domain-id-verify) - `PATCH /api/v1/client/platform-mail/{tenant_id}/category`: Set category (https://www.coritan.com/docs/api/reference/client/mail/platform-mail/#op-patch-api-v1-client-platform-mail-tenant-id-category) - `GET /api/v1/client/platform-mail/{tenant_id}/mailboxes`: List mailboxes (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-mailboxes/#op-get-api-v1-client-platform-mail-tenant-id-mailboxes) - `POST /api/v1/client/platform-mail/{tenant_id}/mailboxes`: Create mailbox (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-mailboxes/#op-post-api-v1-client-platform-mail-tenant-id-mailboxes) - `DELETE /api/v1/client/platform-mail/{tenant_id}/mailboxes/{account_id}`: Delete mailbox (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-mailboxes/#op-delete-api-v1-client-platform-mail-tenant-id-mailboxes-account-id) - `GET /api/v1/client/platform-mail/{tenant_id}/mailboxes/{account_id}/app-passwords`: Mailbox app passwords (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-mailboxes/#op-get-api-v1-client-platform-mail-tenant-id-mailboxes-account-id-app-passwords) - `POST /api/v1/client/platform-mail/{tenant_id}/mailboxes/{account_id}/app-passwords`: Mailbox app password create (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-mailboxes/#op-post-api-v1-client-platform-mail-tenant-id-mailboxes-account-id-app-passwords) - `DELETE /api/v1/client/platform-mail/{tenant_id}/mailboxes/{account_id}/app-passwords/{credential_id}`: Mailbox app password delete (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-mailboxes/#op-delete-api-v1-client-platform-mail-tenant-id-mailboxes-account-id-app-passwords) - `PATCH /api/v1/client/platform-mail/{tenant_id}/mailboxes/{account_id}/enabled`: Mailbox enabled (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-mailboxes/#op-patch-api-v1-client-platform-mail-tenant-id-mailboxes-account-id-enabled) - `GET /api/v1/client/platform-mail/{tenant_id}/mailboxes/{account_id}/imports`: Mailbox imports (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-mailboxes/#op-get-api-v1-client-platform-mail-tenant-id-mailboxes-account-id-imports) - `POST /api/v1/client/platform-mail/{tenant_id}/mailboxes/{account_id}/imports`: Mailbox import start (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-mailboxes/#op-post-api-v1-client-platform-mail-tenant-id-mailboxes-account-id-imports) - `POST /api/v1/client/platform-mail/{tenant_id}/mailboxes/{account_id}/imports/oauth`: Mailbox import sign in (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-mailboxes/#op-post-api-v1-client-platform-mail-tenant-id-mailboxes-account-id-imports-oauth) - `POST /api/v1/client/platform-mail/{tenant_id}/mailboxes/{account_id}/imports/upload`: Mailbox import upload (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-mailboxes/#op-post-api-v1-client-platform-mail-tenant-id-mailboxes-account-id-imports-upload) - `POST /api/v1/client/platform-mail/{tenant_id}/mailboxes/{account_id}/imports/{import_id}/{action}`: Mailbox import steer (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-mailboxes/#op-post-api-v1-client-platform-mail-tenant-id-mailboxes-account-id-imports-import-i) - `POST /api/v1/client/platform-mail/{tenant_id}/mailboxes/{account_id}/password`: Mailbox password (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-mailboxes/#op-post-api-v1-client-platform-mail-tenant-id-mailboxes-account-id-password) - `PATCH /api/v1/client/platform-mail/{tenant_id}/mailboxes/{account_id}/quota`: Mailbox quota (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-mailboxes/#op-patch-api-v1-client-platform-mail-tenant-id-mailboxes-account-id-quota) - `GET /api/v1/client/platform-mail/{tenant_id}/mailboxes/{account_id}/sessions`: Mailbox sessions (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-mailboxes/#op-get-api-v1-client-platform-mail-tenant-id-mailboxes-account-id-sessions) - `DELETE /api/v1/client/platform-mail/{tenant_id}/mailboxes/{account_id}/sessions`: Mailbox sessions end (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-mailboxes/#op-delete-api-v1-client-platform-mail-tenant-id-mailboxes-account-id-sessions) - `DELETE /api/v1/client/platform-mail/{tenant_id}/mailboxes/{account_id}/sessions/{session_id}`: Mailbox session end (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-mailboxes/#op-delete-api-v1-client-platform-mail-tenant-id-mailboxes-account-id-sessions-sessi) - `POST /api/v1/client/platform-mail/{tenant_id}/mailboxes/{account_id}/totp`: Mailbox totp enable (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-mailboxes/#op-post-api-v1-client-platform-mail-tenant-id-mailboxes-account-id-totp) - `DELETE /api/v1/client/platform-mail/{tenant_id}/mailboxes/{account_id}/totp`: Mailbox totp disable (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-mailboxes/#op-delete-api-v1-client-platform-mail-tenant-id-mailboxes-account-id-totp) - `GET /api/v1/client/platform-mail/{tenant_id}/aliases`: List aliases (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-aliases/#op-get-api-v1-client-platform-mail-tenant-id-aliases) - `POST /api/v1/client/platform-mail/{tenant_id}/aliases`: Create alias (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-aliases/#op-post-api-v1-client-platform-mail-tenant-id-aliases) - `DELETE /api/v1/client/platform-mail/{tenant_id}/aliases/{account_id}`: Delete alias (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-aliases/#op-delete-api-v1-client-platform-mail-tenant-id-aliases-account-id) - `GET /api/v1/client/platform-mail/{tenant_id}/credentials`: List credentials (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-credentials/#op-get-api-v1-client-platform-mail-tenant-id-credentials) - `POST /api/v1/client/platform-mail/{tenant_id}/credentials`: Create credential (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-credentials/#op-post-api-v1-client-platform-mail-tenant-id-credentials) - `DELETE /api/v1/client/platform-mail/{tenant_id}/credentials/{account_id}`: Delete credential (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-credentials/#op-delete-api-v1-client-platform-mail-tenant-id-credentials-account-id) - `PATCH /api/v1/client/platform-mail/{tenant_id}/credentials/{account_id}/enabled`: Credential enabled (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-credentials/#op-patch-api-v1-client-platform-mail-tenant-id-credentials-account-id-enabled) - `POST /api/v1/client/platform-mail/{tenant_id}/credentials/{account_id}/rotate`: Rotate credential (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-credentials/#op-post-api-v1-client-platform-mail-tenant-id-credentials-account-id-rotate) - `GET /api/v1/client/platform-mail/{tenant_id}/api-keys`: List API keys (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-api-keys/#op-get-api-v1-client-platform-mail-tenant-id-api-keys) - `POST /api/v1/client/platform-mail/{tenant_id}/api-keys`: Create API key (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-api-keys/#op-post-api-v1-client-platform-mail-tenant-id-api-keys) - `DELETE /api/v1/client/platform-mail/{tenant_id}/api-keys/{key_id}`: Revoke API key (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-api-keys/#op-delete-api-v1-client-platform-mail-tenant-id-api-keys-key-id) - `GET /api/v1/client/platform-mail/{tenant_id}/suppressions`: List suppressions (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-suppressions/#op-get-api-v1-client-platform-mail-tenant-id-suppressions) - `POST /api/v1/client/platform-mail/{tenant_id}/suppressions`: Add suppression (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-suppressions/#op-post-api-v1-client-platform-mail-tenant-id-suppressions) - `DELETE /api/v1/client/platform-mail/{tenant_id}/suppressions/{suppression_id}`: Remove suppression (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-suppressions/#op-delete-api-v1-client-platform-mail-tenant-id-suppressions-suppression-id) - `GET /api/v1/client/platform-mail/{tenant_id}/webhooks`: List webhooks (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-webhooks/#op-get-api-v1-client-platform-mail-tenant-id-webhooks) - `POST /api/v1/client/platform-mail/{tenant_id}/webhooks`: Create webhook (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-webhooks/#op-post-api-v1-client-platform-mail-tenant-id-webhooks) - `DELETE /api/v1/client/platform-mail/{tenant_id}/webhooks/{webhook_id}`: Delete webhook (https://www.coritan.com/docs/api/reference/client/mail/platform-mail-webhooks/#op-delete-api-v1-client-platform-mail-tenant-id-webhooks-webhook-id) - `POST /api/v1/client/platform-mail/{tenant_id}/messages`: Send message (https://www.coritan.com/docs/api/reference/client/mail/platform-mail/#op-post-api-v1-client-platform-mail-tenant-id-messages) - `GET /api/v1/client/platform-mail/{tenant_id}/events`: List events (https://www.coritan.com/docs/api/reference/client/mail/platform-mail/#op-get-api-v1-client-platform-mail-tenant-id-events) - `GET /api/v1/client/platform-mail/{tenant_id}/usage`: Usage report (https://www.coritan.com/docs/api/reference/client/mail/platform-mail/#op-get-api-v1-client-platform-mail-tenant-id-usage) - `GET /api/v1/client/platform-mail/{tenant_id}/reputation`: Reputation report (https://www.coritan.com/docs/api/reference/client/mail/platform-mail/#op-get-api-v1-client-platform-mail-tenant-id-reputation) - `POST /api/v1/client/platform-mail/{tenant_id}/limits/increase-request`: Request limit increase (https://www.coritan.com/docs/api/reference/client/mail/platform-mail/#op-post-api-v1-client-platform-mail-tenant-id-limits-increase-request) # Troubleshoot mail > Fix the common problems with mail domains, mailboxes, mail apps, SMTP Relay sending and webmail, one symptom at a time. Source: https://www.coritan.com/docs/mail/troubleshooting/ Find the symptom or the message you see, then follow the fix. Messages appear here exactly as the dashboard, webmail, your mail app or your application shows them. Each fix links to the page that covers the task in full, and that page's troubleshooting section lists the rest of its messages. ## A domain does not verify After **Verify now**, the toast says `example.com is still pending:` followed by the first problem the check found. The domain's page shows the result of each check in the **Checks** row, and what the last check found in the **Last problem** row. A Mail Hosting domain needs **Ownership**, **MX**, **SPF** and **DKIM** to pass. An SMTP Relay domain needs **Ownership**, **SPF** and **DKIM** ([When a domain is verified](/docs/mail/dns-records/#when-a-domain-is-verified)). `some records have not propagated yet` : The check found a record missing or different, and has no message that names it. The record is the ownership record at `_mailverify.example.com`, the SPF record, or, on Mail Hosting, any MX record at all. A record you created moments ago may not be served yet, or it may hold a typing mistake. Compare each record at your DNS provider with the **DNS records** card, where the **Status** column shows **Missing** for each record the last check did not find. [What each check looks for](/docs/mail/dns-records/#what-each-check-looks-for) gives the rule for each record. `MX points to` followed by other hosts : On Mail Hosting, the domain's MX records name another provider, so the domain cannot verify. Replace them with the ones on the **DNS records** card. If the domain receives mail somewhere else today, follow [Move a domain that already receives mail](/docs/mail/mail-hosting/add-a-domain/#move-a-domain-that-already-receives-mail) first. On SMTP Relay, this is information only. The toast can still name it first while another check keeps the domain pending, so read the **Checks** row. `DKIM selector cor202609r not published` or `No DKIM keys yet: the mail engine has not made them` : We make the domain's two DKIM keys once the ownership check passes. Reload the domain's page to see the two DKIM records, then publish each value whole. When the keys do not exist yet, select **Verify now** again in a minute. `DMARC publishes p=none, expected p=quarantine` : The DMARC record holds a different stage from the one we expect. A domain verifies without DMARC, so this does not keep it pending. [How the DMARC stage ramp works](/docs/mail/dmarc/) explains how to bring the two in line. A verified domain is pending again : Selecting **Verify now** checks a verified domain again, and a required record that has gone, such as the ownership record, sets it back to `pending`. Put the record back, then select **Verify now**. Keep the ownership record in place for as long as the domain is on the service. After you fix a record, select **Verify now** again. We ask your domain's own nameservers, so a record counts as soon as your DNS provider serves it. We also check pending domains about every ten minutes. The messages you can get while adding a domain, such as `That domain is already attached to a mail service`, are in [Add a domain to Mail Hosting](/docs/mail/mail-hosting/add-a-domain/#troubleshooting) and [Add a sending domain to SMTP Relay](/docs/mail/smtp-relay/add-a-sending-domain/#troubleshooting). ## Mail sent to your mailbox does not arrive Check these in order. They apply to Mail Hosting mailboxes and to your coritan.gg mailbox, except where a cause names Mail Hosting. The domain's MX records point to another provider : Other servers deliver the domain's mail wherever its MX records point. On Mail Hosting, the domain then stays `pending` with `MX points to` followed by the other hosts, or, for an older domain, shows **Sending only**. Replace the MX records with the ones on the **DNS records** card. After the change, servers that already queued mail for the old hosts can still deliver it there for a while, so check the old mailboxes for a few days. The address has no mailbox or alias : Mail to an address on a Mail Hosting domain that is neither a mailbox nor an alias bounces back to the sender, unless the domain has a catch-all. Check the spelling with the sender, or create an alias for the address. An alias whose only mailbox was deleted delivers nowhere. [Forward addresses with aliases](/docs/mail/mail-hosting/aliases/) covers both. The mailbox cannot receive mail : A mailbox that is turned off cannot receive mail. Neither can the mailboxes of a Mail Hosting service that is suspended for an unpaid invoice, or whose sending we paused. The mailbox's **Enabled** switch on the **Mailboxes** tab and the **Status** on the service's **Service** card show which applies. See [Turn a mailbox off or on](/docs/mail/mail-hosting/mailboxes/#turn-a-mailbox-off-or-on), [When a payment is late](/docs/mail/billing/#when-a-payment-is-late) and [Sending paused](/docs/mail/smtp-relay/deliverability/#sending-paused). The mailbox is full : A mailbox cannot store more mail than its quota. The line at the bottom of the folder list in webmail shows how much of its storage the mailbox uses. Delete mail you no longer need. On Mail Hosting, you can also give the mailbox a larger quota ([Change a mailbox's quota](/docs/mail/mail-hosting/mailboxes/#change-a-mailbox-s-quota)). The message is in Spam, or a filter moved it : The mail server files mail it takes for spam in **Spam**. Select **Not spam** to move a message back to the Inbox and add its sender to your contacts, whose mail passes the spam filter when it passes DMARC ([Get mail out of Spam](/docs/mail/webmail/read-and-search/#get-mail-out-of-spam)). Mail from a [blocked sender](/docs/mail/webmail/labels-and-filters/#block-a-sender) goes straight to Spam. A filter can move mail to another folder, forward it or discard it as it arrives ([What a filter can do](/docs/mail/webmail/labels-and-filters/#what-a-filter-can-do)). Forwarding all mail can archive or delete the copy in this mailbox ([Forward all mail](/docs/mail/webmail/forwarding/#forward-all-mail)). To search every folder except Spam and Trash, add `in:all` to the search. ## Mail you send does not arrive Find out what happened to the message first. On SMTP Relay, the **Events** tab lists each step of each delivery, up to 90 days back ([Look up message events](/docs/mail/smtp-relay/events/)). On Mail Hosting, the **Recent activity** card on the **Overview** tab shows the latest eight events from the last seven days. Some bounces reach the sending mailbox only as an `Undelivered Mail Returned to Sender` message. The event says what to do next: `rejected`, or no event at all : We refused the message when it was sent, and the reply your application or mail app got says why. See [A message is refused when you send it](#a-message-is-refused-when-you-send-it). A message sent in the last minute or so may not have its events yet. `accepted` with nothing after it : The message is still in the queue, or the receiving server has not answered yet. Check again later. `deferred` : The receiving server refused the message for now, or a sending limit held it back. We try again later. **Response** shows the receiving server's reply, when it gave one. For a sending limit, see [Mail goes out slowly](#mail-goes-out-slowly). `bounced` : The message was not delivered, and we will not try again. **Response** shows the receiving server's reply, when it gave one. We also added the recipient to the suppression list, so we refuse further mail to it ([A recipient is suppressed](#a-recipient-is-suppressed)). `delivered` : The receiving server took the message. Ask the recipient to look in their spam folder, and see [Mail you send goes to spam](#mail-you-send-goes-to-spam). ## Mail you send goes to spam Mail providers decide where a message goes from how it is signed and from how recipients treat your mail. On SMTP Relay, the **Deliverability** card on the **Overview** tab has a check for each of these except the message category and the shared addresses ([The checks](/docs/mail/smtp-relay/deliverability/#the-checks)). The message fails SPF or DKIM : Both pass only while the sending domain's records match the **DNS records** card. Keep one SPF record for the domain, with our `include:` in it, and publish both DKIM records whole ([What each check looks for](/docs/mail/dns-records/#what-each-check-looks-for)). On SMTP Relay, the **SPF and DKIM** check fails when a verified domain is missing either record. The DMARC record is missing or out of date : The **DMARC** check fails when a verified domain's DMARC record is missing or holds another stage. Publish the record that the **DNS records** card shows. [How the DMARC stage ramp works](/docs/mail/dmarc/) explains the stages. Bulk mail goes out as transactional : Gmail and Yahoo ask for one-click unsubscribe headers on bulk mail, and the relay adds them only to marketing mail. Send newsletters and campaigns as `marketing`. Over SMTP, the relay adds the headers only to a message with one recipient, so send bulk mail one message per recipient, or add your own `List-Unsubscribe` headers ([What marketing adds](/docs/mail/smtp-relay/message-categories/#what-marketing-adds)). Recipients report your mail as spam, or it bounces : The **Bounces and complaints** check warns from 2% bounces or 0.1% complaints, which is what Gmail asks of senders. The same rates decide whether we pause your sending ([When sending pauses](/docs/mail/smtp-relay/deliverability/#when-sending-pauses)). [Bring the rates down](/docs/mail/smtp-relay/deliverability/#bring-the-rates-down) lists what helps. The addresses your mail leaves from have a poor reputation : We manage the shared addresses and watch their reputation, so you have nothing to check there. If you think they are the cause, [contact support](/docs/support/conversations/). Mail from a [dedicated address](/docs/mail/smtp-relay/sending-limits/#dedicated-sending-addresses) depends on your own sending alone. A Mail Hosting service has no **Deliverability** card. Its rates are in the reputation report of the API ([Sending reputation and deliverability](/docs/mail/smtp-relay/deliverability/#with-the-api)), and each domain's page shows its records on the **DNS records** card. ## A mail app cannot sign in These apply to Mail Hosting mailboxes and to your coritan.gg mailbox. For an application that sends through SMTP Relay, see [Send over SMTP with credentials](/docs/mail/smtp-relay/credentials/#troubleshooting). The username is not the whole address : Use the mailbox's full address, such as `alex@example.com`, as the username for both servers. Two-factor authentication is on for the mailbox : A mail app cannot send a code, so it needs an app password in place of the mailbox's password. Whoever manages a Mail Hosting service can create one in the dashboard ([Create an app password](/docs/mail/mail-hosting/mailbox-security/#create-an-app-password)). The mailbox's user can create one in webmail ([Create an app password](/docs/mail/webmail/security/#create-an-app-password)). The password changed : After a password reset or a new mailbox password, enter the new password in every app that signs in with it. Apps that use an app password keep working. Your coritan.gg mailbox uses your Coritan account password until you give it one of its own, so changing your account password changes it too ([The mailbox password](/docs/mail/webmail/claim-a-mailbox/#the-mailbox-password)). The mailbox cannot sign in at all : A mailbox that is turned off cannot sign in. Neither can the mailboxes of a service that is suspended or whose sending we paused. Check the mailbox's **Enabled** switch on the **Mailboxes** tab, and the service's **Status**. Too many failed sign-ins : After 30 failed sign-ins in an hour from one IP address, the mail server refuses that address for 24 hours. Fix the password in every app that uses the mailbox, then wait for the block to end. The app cannot connect, or warns about a certificate : Copy the server names from the **Mail server addresses** card on the service's **Overview** tab, or from the **Mail apps** tab of webmail settings. IMAP uses port `993` with SSL/TLS. SMTP uses port `587` with STARTTLS, or port `465` with SSL/TLS. A name under your own domain, such as `imap.example.com`, does not match the certificate, and many networks block port `25`. [Connect a mail app to Mail Hosting](/docs/mail/mail-hosting/connect-a-mail-app/#troubleshooting) and [Set up mail apps and devices](/docs/mail/webmail/mail-apps/#troubleshooting) cover the rest, such as a message too large to send. ## A message is refused when you send it Over SMTP, a refusal is a reply that your application logs or your mail app shows. A mailbox gets the same replies as an SMTP Relay credential, apart from the monthly allowance, which only SMTP Relay has. [Send over SMTP with credentials](/docs/mail/smtp-relay/credentials/#troubleshooting) explains each reply in full. `550 5.7.1 Sender domain example.org is not verified on this account` : The envelope sender, the address in `MAIL FROM`, is not on a domain whose ownership the service has proved. Send from an address on one of the service's domains. `550 5.7.1 From address must use a verified domain, not example.org` : The `From` header is on a domain the service has not proved. Change the From address, or [add the domain](/docs/mail/smtp-relay/add-a-sending-domain/). `550 5.7.1 Sending is not available for this account` : The credential or the mailbox is turned off, or the service is not active. Turn it back on, or check the service's status on the **Overview** tab. `550 5.7.1 Sending is paused for this account; contact support` : We paused the service's sending. See [Sending is paused or under review](#sending-is-paused-or-under-review). `550 5.7.1 Monthly allowance and its 12% buffer are used up; upgrade the plan to continue` or `550 5.7.1 Monthly sending allowance reached; upgrade the plan to continue` : The relay has sent everything its plan allows this month. See [The relay stops sending for the month](#the-relay-stops-sending-for-the-month). `550 5.1.1 Recipient alex@example.com is suppressed after a bounce or complaint` : The recipient is on the service's suppression list. See [A recipient is suppressed](#a-recipient-is-suppressed). `550 5.7.1 This login is not a mail account we know` : The credential belongs to a relay that no longer exists. Create a credential on an active relay. The send API answers the same causes with an HTTP status and a `detail`, such as `400` `example.org is not a domain on this service` or `400` `Sending is paused on this service; contact support`. [Send email over HTTPS](/docs/mail/smtp-relay/send-with-the-api/#troubleshooting) lists each one. In webmail, a refused message opens again in a compose window, with a message that starts `Could not send the message:` and the reason ([Write and send email in webmail](/docs/mail/webmail/write-and-send/#troubleshooting)). ## A recipient is suppressed Over SMTP, we refuse the address with `550 5.1.1 Recipient alex@example.com is suppressed after a bounce or complaint` and accept the other recipients. The send API leaves the address out, sends to the others and lists the address in `suppressed` in its answer ([Read the answer](/docs/mail/smtp-relay/send-with-the-api/#read-the-answer)). Cause : We add an address to the service's suppression list when a message to it bounces, when its recipient reports a message as spam, and when they unsubscribe from marketing mail. You can add addresses too. The reply names a bounce or complaint whatever the reason. Fix : On the relay's **Suppressions** tab, find the address. Its **Reason** says how it got there ([Why an address is on the list](/docs/mail/smtp-relay/suppressions/#why-an-address-is-on-the-list)). Remove it with **Release…** only once the problem is fixed, or once someone who unsubscribed asks to hear from you again ([Remove an address](/docs/mail/smtp-relay/suppressions/#remove-an-address)). The tab lists only the 200 most recent entries. A Mail Hosting service has a suppression list too, which you manage through the API ([On a Mail Hosting service](/docs/mail/smtp-relay/suppressions/#on-a-mail-hosting-service)). ## The relay stops sending for the month Over SMTP, the relay answers `550 5.7.1 Monthly allowance and its 12% buffer are used up; upgrade the plan to continue`, or `550 5.7.1 Monthly sending allowance reached; upgrade the plan to continue` on the free plan. The send API answers `429` with the same text. The **Overview** tab shows **Sending has stopped for this month**, and we email your account's address. Cause : The relay has used its monthly allowance, including the buffer on a paid plan. We check the count plus the recipients of the message in hand, so a message to many recipients can be refused while a message to one recipient still goes out. Fix : Sending starts again at 00:00 UTC on the 1st. To raise the allowance before then, [contact support](/docs/support/conversations/). **Change plan** on the alert opens the **Billing** tab, which cannot move a relay to a larger plan. On the free plan, you can order a paid relay instead ([The free SMTP Relay plan](/docs/mail/billing/#the-free-smtp-relay-plan)). [What happens near the allowance](/docs/mail/smtp-relay/sending-limits/#what-happens-near-the-allowance) lists the warnings we send before this point. ## Mail goes out slowly Cause : A relay sends at most its hourly limit in an hour. Mail over it waits in the queue and goes out in a later hour, and the **Events** tab can show `deferred` events for it meanwhile. Once six hours of mail at the hourly limit is waiting, we refuse new messages until the queue shrinks. A dedicated address in warm-up also has an hourly cap of its own, which can be lower than the relay's limit. A Mail Hosting service has an hourly limit too, and mail over it waits in the same way. Fix : Spread large sends over time. The **Hourly limit** card on the relay's **Overview** tab shows the **Current limit** and the **Next step**. The limit rises on its own as the relay sends clean mail ([How the limit rises](/docs/mail/smtp-relay/sending-limits/#how-the-limit-rises)). To go higher sooner, select **Request limit increase…** ([Request a higher hourly limit](/docs/mail/smtp-relay/request-a-higher-limit/)). Plan large sends from a dedicated address for after its [warm-up](/docs/mail/smtp-relay/sending-limits/#warm-up). ## Sending is paused or under review Above the service's tabs, the **Under review** or **Sending is paused** alert shows the rates that set it off, such as `auto-frozen: 11.4% bounces, 0.08% complaints over 7d`. We do not email you about either. Cause : The service's bounce or complaint rate over the last seven days reached a line: 5% bounces or 0.1% complaints for a review, and 10% bounces or 0.5% complaints for a pause. Our staff can also pause a service by hand. Under review, sending carries on. While sending is paused, relay credentials cannot sign in, and the send API answers `400` `Sending is paused on this service; contact support`. On Mail Hosting, the mailboxes cannot sign in, send or receive mail. Fix : Find and fix what caused the bounces or complaints ([Bring the rates down](/docs/mail/smtp-relay/deliverability/#bring-the-rates-down)). Only our staff lift a pause or clear a review, so then [contact support](/docs/support/conversations/) and say what you changed. If the rates of the last seven days are still at a pause line when our staff lift the pause, the next check pauses sending again ([Sending paused](/docs/mail/smtp-relay/deliverability/#sending-paused)). ## Webmail does not open a mailbox When webmail at [www.coritan.com/webmail](https://www.coritan.com/webmail) cannot open a mailbox, it shows `Could not open your mailbox`, the reason under it, and **Try again**. `This mailbox is disabled.` : The mailbox is turned off. Whoever manages a Mail Hosting service turns it back on with the mailbox's **Enabled** switch ([Turn a mailbox off or on](/docs/mail/mail-hosting/mailboxes/#turn-a-mailbox-off-or-on)). When we turned the mailbox off because it suddenly sent far more mail than usual, the reason gives how many messages it sent in an hour instead. Reset its password, then turn it back on ([Create and manage mailboxes](/docs/mail/mail-hosting/mailboxes/#troubleshooting)). For your coritan.gg mailbox, [contact support](/docs/support/conversations/). `The mail service is not reachable right now.` : The mail server that holds the mailbox did not answer. Select **Try again** after a moment, and [contact support](/docs/support/conversations/) if it keeps happening. Another reason : Select **Try again**. If the same reason comes back, contact support and quote it. A mailbox in the account menu reads `Disabled, so it cannot be opened` or `Not reachable right now` : The first is turned off, as above. We cannot reach the second at the moment. Neither opens until that changes ([What each mailbox is](/docs/mail/webmail/mailboxes-and-sharing/#what-each-mailbox-is)). `Claim your @coritan.gg address` or `No mailbox in your account yet` : Your Coritan account has no mailbox yet. Pick a name to get your coritan.gg mailbox ([Get a coritan.gg mailbox](/docs/mail/webmail/claim-a-mailbox/)). To read a mailbox you manage, such as one on Mail Hosting, select **Add a mailbox** and enter its address and password ([Add a mailbox to your account](/docs/mail/webmail/mailboxes-and-sharing/#add-a-mailbox-to-your-account)). mail.coritan.com or webmail.coritan.com opens www.coritan.com/webmail : Webmail lives at www.coritan.com/webmail, and both addresses send you there, to the same page the link named. Update your bookmarks to [www.coritan.com/webmail](https://www.coritan.com/webmail). ## You cannot sign in to webmail `That address and password do not match. Check both and try again.` : Enter the whole address, such as `alex@example.com`, and check the password. We give the same answer for a mailbox that is turned off. For your coritan.gg mailbox, the password is your Coritan account password until you give the mailbox one of its own. A message that starts `Too many attempts to sign in.` : After 10 failed attempts at one address within 15 minutes, or 20 from one IP address within 5 minutes, we refuse sign-ins for a while. The message says when you can try again. [Sign in to webmail](/docs/mail/webmail/sign-in/#troubleshooting) lists the other messages, and what to do when you have forgotten the password. ## None of these match [Contact support](/docs/support/conversations/) and include: - The **Service ID** from the service's **Billing** tab, or the address of the mailbox involved. - The domain, and the sender and the recipient of a message that went wrong. - The whole message or SMTP reply, exactly as it appeared, and when it happened. - For a message sent through the send API, the `message_id` from its answer. Never send a password, an app password or an API key, to us or to anyone else. # Websites > Register and host domains, manage DNS records and DNSSEC, balance traffic, issue SSL/TLS certificates and put a web proxy and WAF in front of a site. Source: https://www.coritan.com/docs/websites/ In the dashboard: - /dashboard/websites: https://www.coritan.com/dashboard/websites Websites holds every domain on your account. You can register a domain with Coritan, transfer one in, or leave it at another registrar and host only its DNS here. A domain hosted here can then have DNS records, load balancers, SSL/TLS certificates, web proxies and a web application firewall (WAF). ## What you can do ### Domains - [How domain registration works](/docs/websites/domains/) - [Register a domain](/docs/websites/domains/register-a-domain/) - [Transfer a domain to Coritan](/docs/websites/domains/transfer-a-domain-to-coritan/) - [Renew a domain](/docs/websites/domains/renew-a-domain/) - [Change a domain's nameservers](/docs/websites/domains/change-nameservers/) - [Turn on WHOIS privacy and transfer lock](/docs/websites/domains/whois-privacy-and-transfer-lock/) - [Transfer a domain to another registrar](/docs/websites/domains/transfer-a-domain-away/) ### DNS - [Add a domain you already own](/docs/websites/add-an-existing-domain/) - [How DNS hosting works](/docs/websites/dns/) - [Add, edit and delete DNS records](/docs/websites/dns/manage-dns-records/) - [DNS record types](/docs/websites/dns/record-types/) - [Import and export a zone file](/docs/websites/dns/import-and-export-a-zone-file/) - [Set up DNSSEC](/docs/websites/dns/dnssec/) - [View DNS query statistics](/docs/websites/dns-query-statistics/) - [Delete a DNS zone](/docs/websites/dns/delete-a-dns-zone/) ### Traffic and security - [How DNS load balancing works](/docs/websites/load-balancing/), then [create an origin pool](/docs/websites/load-balancing/create-an-origin-pool/) and [a load balancer](/docs/websites/load-balancing/create-a-load-balancer/) - [How SSL/TLS certificates work](/docs/websites/ssl/), [issue a certificate](/docs/websites/ssl/issue-a-certificate/) and [renew or revoke one](/docs/websites/ssl/renew-or-revoke-a-certificate/) - [Manage a website's web proxy](/docs/websites/proxy/) - [Protect a website with the WAF](/docs/websites/waf/) ### Look things up - [Website settings](/docs/websites/settings/) - [Load balancing reference](/docs/websites/load-balancing/reference/) - [Troubleshoot websites and DNS](/docs/websites/troubleshooting/) A free name under `coritan.gg` also appears in this list once you claim it. [Claim a free coritan.gg name](/docs/proxies/coritan-gg-names/) explains how. ## The Websites list In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites). Each row is one domain, and the line under its name says how Coritan holds it: | Line under the name | What it means | | --- | --- | | **Registered and hosted here** | Coritan is the domain's registrar and hosts its DNS zone. | | **Registered here · DNS elsewhere** | Coritan is the registrar, and another provider's nameservers answer for the domain. | | **DNS hosted here** | Another registrar holds the domain, and Coritan hosts its DNS zone. | The **DNS** column shows how many records the zone holds, with **DNSSEC signed** under it when the zone is signed, or **Elsewhere**. **Expires** shows the registration's expiry date and warns when fewer than 30 days are left. **Registrar** shows the registration's **Auto-renew**, **Locked** and **WHOIS privacy** badges, or **Manual renewal** when none of them is on. Filter the list with **Registered here**, **DNS only** or **Expiring**, or search it by domain name. The buttons above the list are **Register domain…** and **Add existing domain…**. The **More website actions** menu holds **Transfer domain…**, **Claim another name…**, **View domain prices** and **Refresh**. Select a domain to open it. Its tabs are **Overview**, **DNS**, **Load balancing**, **SSL**, **Proxy**, **WAF** and **Settings**, and **Visit site** opens the domain in a new browser tab. ## Billing Registering a domain takes the price for the whole period from your account credit before Coritan registers it, so [add credit](/docs/billing/add-credit/) first. **View domain prices** lists what each domain extension costs per year to register, renew and transfer. ## Limits - An account can host 100 DNS zones, and a zone can hold 1,000 records. When you reach a limit, the error names it. - You cannot add a zone for Coritan's own domains or for a name under them. For a name under `coritan.gg`, [claim it](/docs/proxies/coritan-gg-names/) instead. - You cannot add a zone inside another account's zone, or the parent of one. # Add a domain you already own > Host the DNS for a domain registered elsewhere on Coritan, then point the domain's nameservers here. Source: https://www.coritan.com/docs/websites/add-an-existing-domain/ In the dashboard: - /dashboard/websites: https://www.coritan.com/dashboard/websites Add a domain registered with another registrar when you want Coritan to host its DNS. Coritan creates a DNS zone for the domain, and the registration stays where it is. Once you set Coritan's nameservers at your registrar, the zone's records answer for the domain. To move the registration to Coritan as well, [transfer the domain](/docs/websites/domains/transfer-a-domain-to-coritan/). ## Before you begin - Make sure you can change the domain's nameservers at its current registrar. - Write down the records the domain uses today, such as its website address, its mail servers and any verification `TXT` records. Add them to the new zone before you change the nameservers, or the domain stops resolving until you do. If your current DNS provider can export a BIND zone file, you can [import it](/docs/websites/dns/import-and-export-a-zone-file/). ## Add the domain 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites) and select **Add existing domain…**. 2. Enter the **Domain name**, such as `example.com`, and select **Add domain**. 3. The dialog now lists the nameservers to set at your registrar. Copy them. 4. Select **Open DNS** to go to the zone's **DNS** tab, and add the records the domain needs. [Add, edit and delete DNS records](/docs/websites/dns/manage-dns-records/) explains the form. 5. At your registrar, replace the domain's nameservers with Coritan's. > [!NOTE] > The dialog lists two nameservers. The zone's `NS` records, at `@` on its **DNS** tab, list every Coritan nameserver (`ns1.coritan.com` to `ns8.coritan.com`), and you can give your registrar the full set. ## Result The domain appears in the Websites list as **DNS hosted here**. Coritan signs the new zone with DNSSEC from the start; [Set up DNSSEC](/docs/websites/dns/dnssec/) explains the step left at your registrar. The switch happens once your registrar publishes the new nameservers, and resolvers keep the old ones until their cached answers expire. Check the delegation from a terminal: ```bash dig NS example.com +short ``` When the answer lists Coritan's nameservers, the zone is live. The domain's **Overview** tab then starts to count queries ([View DNS query statistics](/docs/websites/dns-query-statistics/)). ## Troubleshooting `Enter a domain, such as example.com.` : The dashboard could not read the name as a domain. Enter the bare domain, without `https://` or a path. `Domain already registered` : A DNS zone for this domain already exists on Coritan. If it is yours, it is in your Websites list. If another account holds it, contact [support](/docs/support/). `This domain is reserved by the platform` : The domain is one of Coritan's own, or a name under one. For a name under `coritan.gg`, [claim a free name](/docs/proxies/coritan-gg-names/) instead. `Conflicts with the existing zone example.com, which belongs to another account` : Another account hosts a zone that contains your domain, or a zone inside it. Coritan refuses zones that overlap across accounts. `Zone limit reached (100)` : The account hosts as many zones as it may. [Delete a zone](/docs/websites/dns/delete-a-dns-zone/) you no longer use. ## Related - [How DNS hosting works](/docs/websites/dns/) - [Register a domain](/docs/websites/domains/register-a-domain/) - [Troubleshoot websites and DNS](/docs/websites/troubleshooting/) ## With the API Create the zone with an access token ([Authentication](/docs/api/authentication/)). The body takes one field, `domain`: ```bash curl -X POST https://api.coritan.com/api/v1/dns/zones \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"domain": "example.com"}' ``` The answer is `201` with the zone. Keep its `id`: every other DNS call names the zone by it. ```json { "id": 42, "domain": "example.com", "status": "active", "dnssec_enabled": true, "serial": 2026092501, "soa_email": "hostmaster", "record_count": 8 } ``` The zone starts with its `NS` records, one for each Coritan nameserver. List your zones, sorted by domain, with `GET /api/v1/dns/zones`. It returns 25 zones a page by default; `per_page` takes up to 100, and `page` counts from 1. ```bash curl "https://api.coritan.com/api/v1/dns/zones?per_page=100" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ## API - `POST /api/v1/dns/zones`: Create zone (https://www.coritan.com/docs/api/reference/client/dns/#op-post-api-v1-dns-zones) - `GET /api/v1/dns/zones`: List zones (https://www.coritan.com/docs/api/reference/client/dns/#op-get-api-v1-dns-zones) # View DNS query statistics > See how many DNS queries a hosted zone answered, by response code and by hour, on a website's Overview tab. Source: https://www.coritan.com/docs/websites/dns-query-statistics/ In the dashboard: - /dashboard/websites/…/overview: https://www.coritan.com/dashboard/websites A website's **Overview** tab counts the DNS queries Coritan's nameservers answered for its zone over a period you choose. Use it to confirm that resolvers have found your zone after a nameserver change, and to spot names that do not exist or answers that fail. ## Before you begin The domain's DNS must be hosted on Coritan. For a domain whose DNS is elsewhere, the tab shows **No query statistics**. ## Read the statistics 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites) and select the domain. The **Overview** tab opens. 2. Choose a **Time range**: **1 hour**, **6 hours**, **24 hours**, **48 hours**, **7 days** or **30 days**. The tab opens on **24 hours**. 3. Read the four counters for that period: - **Total queries**: every query the zone received. - **NOERROR**: queries the zone answered. - **NXDOMAIN**: queries for a name that does not exist in the zone. - **SERVFAIL**: queries the nameserver could not answer. 4. Read the **Query analytics** chart, which draws the same four counts hour by hour. The **Nameservers** card below the chart lists the domain's nameservers when Coritan is its registrar. For a domain registered elsewhere, it says you set the nameservers at the other registrar. ## Result The counters and the chart cover the range you chose, in hourly steps. A zone that no resolver has asked about yet shows **No queries in this range**. ## Troubleshooting **No queries in this range** : No resolver asked for the zone in that period. Check that the registrar lists Coritan's nameservers ([Change a domain's nameservers](/docs/websites/domains/change-nameservers/)), then choose a longer range. A high **NXDOMAIN** count : Clients ask for names the zone does not have. Compare the names your services use with the zone's records on the **DNS** tab. `Could not load the zone statistics` : The request failed. Reload the page to try again. ## Related - [How DNS hosting works](/docs/websites/dns/) - [Add, edit and delete DNS records](/docs/websites/dns/manage-dns-records/) ## With the API Read the statistics for the last `hours` hours, from 1 to 720 (24 when you leave it out): ```bash curl "https://api.coritan.com/api/v1/dns/zones/42/stats?hours=168" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "zone_id": 42, "period_hours": 168, "total_queries": 18250, "noerror": 17904, "nxdomain": 331, "servfail": 15, "by_type": {"A": 9120, "AAAA": 6210, "MX": 1402, "TXT": 1518}, "hourly": [ {"hour": "2026-09-25 14:00:00", "total": 131, "noerror": 128, "nxdomain": 3, "servfail": 0} ] } ``` `by_type` counts the period's queries by record type. `hourly` holds one entry per hour that had queries, newest first. ## API - `GET /api/v1/dns/zones/{zone_id}/stats`: Get zone stats (https://www.coritan.com/docs/api/reference/client/dns/#op-get-api-v1-dns-zones-zone-id-stats) # How domain registration works > What happens when you register or transfer a domain with Coritan, how prices are set, and what each registration status means. Source: https://www.coritan.com/docs/websites/domains/ Coritan registers domains for you with each extension's registry, renews them and hands them over when you move them elsewhere. A domain you register or transfer here is a *registration* on your account. Its DNS can be hosted on Coritan or anywhere else, because the registration only says which *nameservers* answer for the domain. ## Terms Registration : Your right to a domain for the period you pay for, 1–10 years at a time, held through Coritan. Registry : The organisation that runs a domain extension, such as `.com`. Coritan passes your changes to it. Nameservers : The servers the registry sends resolvers to for the domain. A domain registered here starts on Coritan's nameservers, `ns1.coritan.com` to `ns8.coritan.com`. EPP code : A secret the current registrar gives you so another registrar can take the domain over. It is also called an auth code. Transfer lock : A registry setting that refuses transfer requests while it is on. ## Prices The dashboard shows a domain's price per year when you search for it. A *premium* domain has a price that its registry sets. **View domain prices**, in the Websites list's **More website actions** menu, lists what each extension costs per year to register, renew and transfer. A registration takes the price for the whole period from your [account credit](/docs/billing/add-credit/) before Coritan registers the domain, and gives it back when the registration fails. ## What registering sets up A new registration starts with the transfer lock and auto-renew on, and with WHOIS privacy on when you ask for it. The dashboard also offers to issue a certificate and to set up a web proxy for the domain. When either of those is on, Coritan creates the domain's DNS zone too. [Register a domain](/docs/websites/domains/register-a-domain/) describes each option. ## Statuses The status appears beside the domain in the Websites list and on its **Settings** tab. The API sends these words: | Status | Meaning | | --- | --- | | `active` | The domain is registered and you can change its settings. | | `expiring_soon` | The domain expires in fewer than 30 days. | | `expired` | The registry reports the domain as expired. | | `pending_transfer` | You started a transfer to Coritan and it has not completed. | Coritan reads each registration back from its registry regularly, and updates its status, expiry date, nameservers and switches from what the registry holds. A change made at the registry shows in the dashboard after that read. > [!IMPORTANT] > Renewing, the switches, the nameservers and the EPP code all need the registration to be `active`. While it is `expiring_soon`, `expired` or `pending_transfer`, the dashboard cannot change them; contact [support](/docs/support/) if you need to. ## Renewal and expiry With auto-renew on, Coritan renews the domain for one year when it is within 14 days of its expiry date. With auto-renew off, Coritan emails you 30, 14, 7, 3 and 1 day before the domain expires. [Renew a domain](/docs/websites/domains/renew-a-domain/) covers both. ## Read registrations with the API List your registrations, newest first, with an access token ([Authentication](/docs/api/authentication/)). The list takes `page`, `per_page` (25 by default, up to 100) and `status`, which keeps only registrations with that status word: ```bash curl "https://api.coritan.com/api/v1/domains/registrations?status=active" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "registrations": [ { "id": 7, "domain": "example.com", "status": "active", "registered_at": "2026-09-16T10:04:00Z", "expires_at": "2027-09-16T10:04:00Z", "nameservers": ["ns1.coritan.com", "ns2.coritan.com"], "whois_privacy": true, "locked": true, "auto_renew": true, "dns_zone_id": 42, "proxy_route_id": null, "ssl_certificate_id": null } ], "total": 1 } ``` `GET /api/v1/domains/registrations/{reg_id}` returns one registration in the same shape, or `404` with `Registration not found`. `GET /api/v1/domains/pricing` returns the price list, one entry per extension, sorted by extension: ```json [ {"tld": "com", "register_price": "14.99", "renew_price": "16.99", "transfer_price": "14.99", "currency": "USD"} ] ``` The figures in this example only show the shape. The endpoint returns the current prices, as strings, and `transfer_price` is `null` for an extension that has none. ## Related - [Register a domain](/docs/websites/domains/register-a-domain/) - [Transfer a domain to Coritan](/docs/websites/domains/transfer-a-domain-to-coritan/) - [Website settings](/docs/websites/settings/) ## API - `GET /api/v1/domains/pricing`: Get available TLD pricing (https://www.coritan.com/docs/api/reference/client/domains/#op-get-api-v1-domains-pricing) - `GET /api/v1/domains/registrations`: List user's domain registrations (https://www.coritan.com/docs/api/reference/client/domains/#op-get-api-v1-domains-registrations) - `GET /api/v1/domains/registrations/{reg_id}`: Get details of a specific domain registration (https://www.coritan.com/docs/api/reference/client/domains/#op-get-api-v1-domains-registrations-reg-id) # Register a domain > Search for a free domain name, see its price and register it with Coritan from the Websites page. Source: https://www.coritan.com/docs/websites/domains/register-a-domain/ In the dashboard: - /dashboard/websites: https://www.coritan.com/dashboard/websites Register a domain when you want Coritan to hold its registration as well as its DNS. The domain is yours for the period you pay for, starts on Coritan's nameservers, and can have its DNS zone, a certificate and a web proxy set up at the same time. ## Before you begin - Make sure your account credit covers the price for the whole period. Coritan takes it before it registers the domain ([Add credit](/docs/billing/add-credit/)). - Check the prices: select **View domain prices** in the Websites list's **More website actions** menu. ## Register the domain 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites) and select **Register domain…**. 2. Enter the **Domain name**, such as `example.com`, and select **Search**. 3. Read the result. The dialog says whether the domain is available, shows its **Price per year**, and marks a premium domain, whose price its registry sets. If it is taken, select **Back** and search for another name. 4. Choose the **Registration period**, from 1 to 10 years. 5. Choose the options: - **Issue an SSL certificate** (on at first) requests a certificate for the web proxy that the next option sets up. - **Set up a proxy route** (off at first) creates a web proxy for the domain. - **WHOIS privacy** (off at first) hides your contact details from public WHOIS lookups. 6. Select **Register domain**. ## Result The dashboard confirms that the domain is registered, and it appears in the Websites list. The registration starts with: - the transfer lock and auto-renew on, and WHOIS privacy on if you chose it; - Coritan's nameservers, `ns1.coritan.com` to `ns8.coritan.com`; - a DNS zone hosted on Coritan, signed with DNSSEC, when **Issue an SSL certificate** or **Set up a proxy route** was on. > [!IMPORTANT] > With both options off, Coritan creates no DNS zone, so the domain does not resolve. Select **Add existing domain…** and enter the domain to create its zone ([Add a domain you already own](/docs/websites/add-an-existing-domain/)). The web proxy that **Set up a proxy route** creates has no origin of yours yet, so visitors see an error until you [set its origin](/docs/proxies/web-proxies/change-the-origin/). Coritan requests the certificate only for that proxy. With **Set up a proxy route** off, [issue the certificate](/docs/websites/ssl/issue-a-certificate/) from the domain's **SSL** tab once you need one. Next, [add the domain's DNS records](/docs/websites/dns/manage-dns-records/). ## Troubleshooting `Insufficient credit balance. Required: 22.49 USD, available: 10.00` : The account credit does not cover the price for the period. [Add credit](/docs/billing/add-credit/), or choose a shorter period. `Domain is not available for registration` : Someone else holds the domain. Search for another name. `Domain already registered in our system` : The domain is already registered through Coritan, on your account or another. `Registration failed: …` or `Registrar error: …` : The registry refused the registration or did not answer. Coritan gives back the charge. Try again later. `Invalid domain name format` : Enter the bare domain, such as `example.com`, without `https://` or a path. ## Related - [How domain registration works](/docs/websites/domains/) - [Renew a domain](/docs/websites/domains/renew-a-domain/) - [Turn on WHOIS privacy and transfer lock](/docs/websites/domains/whois-privacy-and-transfer-lock/) ## With the API Check the name first. The price is per year, as a string: ```bash curl -X POST https://api.coritan.com/api/v1/domains/search \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"domain": "example.com"}' ``` ```json {"domain": "example.com", "available": true, "price": "14.99", "currency": "USD", "premium": false} ``` Then register it. The body takes `domain`, `duration` (1–10 years, 1 when you leave it out), `whois_privacy`, `auto_ssl`, `auto_proxy` and, optionally, `nameservers`, a list of the nameservers to start with instead of Coritan's. > [!NOTE] > The API's defaults differ from the dashboard's: `auto_ssl`, `auto_proxy` and `whois_privacy` are all `true` when you leave them out. Send each one you want off as `false`. ```bash curl -X POST https://api.coritan.com/api/v1/domains/register \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"domain": "example.com", "duration": 2, "whois_privacy": true, "auto_ssl": true, "auto_proxy": false}' ``` The answer is `201` with the registration, in the shape [How domain registration works](/docs/websites/domains/#read-registrations-with-the-api) shows. `dns_zone_id` names the DNS zone Coritan created, and `proxy_route_id` the web proxy. ## API - `POST /api/v1/domains/search`: Check domain availability and pricing (includes markup) (https://www.coritan.com/docs/api/reference/client/domains/#op-post-api-v1-domains-search) - `POST /api/v1/domains/register`: Register a new domain name (https://www.coritan.com/docs/api/reference/client/domains/#op-post-api-v1-domains-register) # Transfer a domain to Coritan > Move a domain from another registrar to Coritan with the EPP code that registrar gives you. Source: https://www.coritan.com/docs/websites/domains/transfer-a-domain-to-coritan/ In the dashboard: - /dashboard/websites: https://www.coritan.com/dashboard/websites Transfer a domain to Coritan when you want Coritan to hold its registration instead of your current registrar. The transfer needs the domain's EPP code, and the current registrar has to release the domain. To host only the domain's DNS here and keep the registration where it is, [add it as an existing domain](/docs/websites/add-an-existing-domain/) instead. ## Before you begin - At your current registrar, turn off the domain's transfer lock and ask for its EPP code (also called an auth code). - Your current registrar can tell you whether the registry allows the transfer yet. - Check the transfer price: **View transfer prices** in the transfer dialog lists it for each extension. - To keep the domain resolving during the transfer, leave its nameservers as they are, or move its DNS to Coritan first with **Add existing domain…**. Starting the transfer does not change the nameservers. ## Start the transfer 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites), open the **More website actions** menu and select **Transfer domain…**. 2. Enter the **Domain name**, such as `example.com`. 3. Enter the **EPP code** exactly as the current registrar gave it to you. 4. Select **Transfer domain**. ## Result The dashboard confirms that the transfer started, and the domain appears in the Websites list with the status `pending_transfer`. Your current registrar may email you to approve the transfer; approve it to speed it up. While the transfer is pending, the domain's **Settings** tab cannot renew it or change its switches or nameservers. Coritan does not move the registration out of `pending_transfer` by itself. When your old registrar confirms that the domain has left, contact [support](/docs/support/) so the registration can be made active. ## Troubleshooting `Domain already exists in our system` : The domain is already registered, or being transferred, through Coritan. `Transfer failed: …` : The registry refused the transfer. The usual causes are a wrong EPP code or a transfer lock that is still on at the current registrar. Check both and try again. `Invalid domain name format` : Enter the bare domain, such as `example.com`, without `https://` or a path. ## Related - [How domain registration works](/docs/websites/domains/) - [Transfer a domain to another registrar](/docs/websites/domains/transfer-a-domain-away/) ## With the API The body takes the `domain` and its `auth_code`: ```bash curl -X POST https://api.coritan.com/api/v1/domains/transfer \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"domain": "example.com", "auth_code": "Xk7#pQ2m9L"}' ``` The answer is `202` with the new registration's id: ```json {"message": "Transfer initiated", "domain": "example.com", "registration_id": 8, "status": "pending_transfer"} ``` Follow it with `GET /api/v1/domains/registrations/8`. ## API - `POST /api/v1/domains/transfer`: Initiate an inbound domain transfer (https://www.coritan.com/docs/api/reference/client/domains/#op-post-api-v1-domains-transfer) # Renew a domain > Renew a domain registered with Coritan for one to ten years, or turn on auto-renew so it renews before it expires. Source: https://www.coritan.com/docs/websites/domains/renew-a-domain/ In the dashboard: - /dashboard/websites/…/settings: https://www.coritan.com/dashboard/websites Renew a domain to extend its registration past its expiry date. You can renew it by hand for 1–10 years at a time, or leave auto-renew on so that Coritan renews it before it expires. When a domain expires, its registry can stop it resolving and later release it for anyone to register. ## Before you begin - The domain must be registered with Coritan. For a domain registered elsewhere, renew it at that registrar. - The registration must be `active`. See [Troubleshooting](#troubleshooting) when it is not. ## Renew by hand 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites), open the domain and select the **Settings** tab. 2. In the **Renewal** card, choose how many years to add, from 1 to 10. 3. Select **Renew domain**. The dashboard confirms the renewal. The **Expires** date updates once Coritan next reads the registration back from the registry. ## Turn auto-renew on or off 1. On the domain's **Settings** tab, find the **Domain options** card. 2. Turn **Auto-renew** on or off. With auto-renew on, Coritan renews the domain for one year when it is within 14 days of its expiry date. It tries again the next day when a renewal fails. With auto-renew off, Coritan emails you 30, 14, 7, 3 and 1 day before the domain expires. ## Result The **Registration** card on the **Settings** tab shows the new **Expires** date and whether **Auto-renew** is on. In the Websites list, the **Registrar** column shows the **Auto-renew** badge, or **Manual renewal**. ## Troubleshooting `Registration not found or not active` : The dashboard renews only a registration whose status is `active`. Within 30 days of its expiry date a registration is `expiring_soon`, and once the registry reports it expired it is `expired`. Contact [support](/docs/support/) to renew a domain in either state. `Could not save the Auto-renew setting` : The switch could not be changed. The registration may not be `active`, or the registry did not answer. Try again later. `Renewal failed: …` : The registry refused the renewal or did not answer. The expiry date has not changed. Try again later. `Duration must be between 1 and 10 years` : The API accepts a `duration` from 1 to 10. ## Related - [How domain registration works](/docs/websites/domains/) - [Website settings](/docs/websites/settings/) ## With the API Renew registration `7` for two years. The body's `duration` is 1 when you leave it out: ```bash curl -X POST https://api.coritan.com/api/v1/domains/registrations/7/renew \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"duration": 2}' ``` ```json {"message": "Domain renewed successfully", "domain": "example.com", "new_expiry": "2029-09-16T10:04:00+00:00"} ``` The answer is `202`, and `new_expiry` is the date the registry reported, or `null` when it reported none. Turn auto-renew on or off with `enabled`: ```bash curl -X PUT https://api.coritan.com/api/v1/domains/registrations/7/auto-renew \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"enabled": false}' ``` ```json {"message": "Auto-renew disabled", "domain": "example.com"} ``` ## API - `POST /api/v1/domains/registrations/{reg_id}/renew`: Request domain renewal (https://www.coritan.com/docs/api/reference/client/domains/#op-post-api-v1-domains-registrations-reg-id-renew) - `PUT /api/v1/domains/registrations/{reg_id}/auto-renew`: Toggle auto-renewal for a domain (https://www.coritan.com/docs/api/reference/client/domains/#op-put-api-v1-domains-registrations-reg-id-auto-renew) # Change a domain's nameservers > Point a domain registered with Coritan at Coritan's nameservers or at another DNS provider's. Source: https://www.coritan.com/docs/websites/domains/change-nameservers/ In the dashboard: - /dashboard/websites/…/settings: https://www.coritan.com/dashboard/websites A domain's nameservers decide which DNS provider answers for it. Change them when you move the domain's DNS to another provider, or back to Coritan. This page covers domains registered with Coritan; for a domain registered elsewhere, change its nameservers at that registrar. ## Before you begin - The registration must be `active` ([Statuses](/docs/websites/domains/#statuses)). - Make sure the new provider already holds the domain's records. Resolvers follow the new nameservers as soon as they see them. - To use Coritan's DNS, the domain needs a zone on Coritan. The **DNS zone** row of the **Registration** card says **Hosted here** when it has one. If it does not, [add the domain as an existing domain](/docs/websites/add-an-existing-domain/) first. ## Change the nameservers 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites), open the domain and select the **Settings** tab. 2. In the **Nameservers** card, select **Edit nameservers**. 3. Change the nameservers: - Type over a nameserver to replace it, such as `ns1.example.net`. - Select **Add nameserver** to add another, up to 13. - Select the remove button beside a nameserver to take it off the list. The card keeps at least two. 4. Select **Save nameservers**. To go back to Coritan's DNS, enter `ns1.coritan.com` to `ns8.coritan.com`. ## Result The dashboard confirms that the nameservers are saved, and the card lists the new set. Resolvers keep the old nameservers until their cached answers expire. Check what the registry now publishes from a terminal: ```bash dig NS example.com +short ``` Once the domain points elsewhere, the records on its **DNS** tab no longer answer for it, and neither do Coritan's load balancers and proxied records for its names. ## Troubleshooting `Enter at least two nameservers.`, `Enter at most 13 nameservers.` or `Fill in every nameserver, or remove the empty ones.` : A domain takes 2–13 nameservers, and none of them can be empty. `Registration not found` : The registration is not `active`. See [Statuses](/docs/websites/domains/#statuses). `Failed to update nameservers: …` : The registry refused the change or did not answer. Check that each name is a working nameserver, then try again. ## Related - [How DNS hosting works](/docs/websites/dns/) - [Transfer a domain to another registrar](/docs/websites/domains/transfer-a-domain-away/) ## With the API Send the full list, from 2 to 13 names. Coritan lowercases them and drops a trailing dot. ```bash curl -X PUT https://api.coritan.com/api/v1/domains/registrations/7/nameservers \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"nameservers": ["ns1.example.net", "ns2.example.net"]}' ``` ```json {"message": "Nameservers updated", "domain": "example.com", "nameservers": ["ns1.example.net", "ns2.example.net"]} ``` ## API - `PUT /api/v1/domains/registrations/{reg_id}/nameservers`: Update nameservers for a domain (https://www.coritan.com/docs/api/reference/client/domains/#op-put-api-v1-domains-registrations-reg-id-nameservers) # Turn on WHOIS privacy and transfer lock > Hide your contact details from WHOIS and stop anyone transferring a domain away while it is locked. Source: https://www.coritan.com/docs/websites/domains/whois-privacy-and-transfer-lock/ In the dashboard: - /dashboard/websites/…/settings: https://www.coritan.com/dashboard/websites Two switches protect a domain registered with Coritan. *WHOIS privacy* hides your contact details from public WHOIS lookups. The *transfer lock* makes the registry refuse transfer requests from other registrars. A new registration starts with the transfer lock on, and with WHOIS privacy on if you chose it when you registered. ## Before you begin The domain must be registered with Coritan, and its registration must be `active` ([Statuses](/docs/websites/domains/#statuses)). ## Change the switches 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites), open the domain and select the **Settings** tab. 2. In the **Domain options** card: - Turn **WHOIS privacy** on to hide your contact details, or off to show them. - Turn **Transfer lock** on to refuse transfers, or off before you move the domain to another registrar. ## Result The dashboard confirms each change. The **Registration** card shows **WHOIS privacy** as `On` or `Off` and **Transfer lock** as `Locked` or `Unlocked`, and the Websites list shows the **WHOIS privacy** and **Locked** badges. Keep the transfer lock on unless you are moving the domain. [Transfer a domain to another registrar](/docs/websites/domains/transfer-a-domain-away/) explains when to turn it off. ## Troubleshooting `Could not save the WHOIS privacy setting` or `Could not save the Transfer lock setting` : The change did not reach the registry. The registration may not be `active`, or the registry did not answer. Try again later. `Failed to update WHOIS privacy: …` or `Failed to update lock: …` : The registry refused the change. Contact [support](/docs/support/) if it keeps failing. ## Related - [How domain registration works](/docs/websites/domains/) - [Website settings](/docs/websites/settings/) ## With the API Both calls take `enabled`: ```bash curl -X PUT https://api.coritan.com/api/v1/domains/registrations/7/whois-privacy \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"enabled": true}' ``` ```json {"message": "WHOIS privacy enabled", "domain": "example.com"} ``` ```bash curl -X PUT https://api.coritan.com/api/v1/domains/registrations/7/lock \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"enabled": false}' ``` ```json {"message": "Domain unlocked", "domain": "example.com"} ``` Both answer `404` with `Registration not found` when the registration is not yours or not `active`. ## API - `PUT /api/v1/domains/registrations/{reg_id}/whois-privacy`: Toggle WHOIS privacy for a domain (https://www.coritan.com/docs/api/reference/client/domains/#op-put-api-v1-domains-registrations-reg-id-whois-privacy) - `PUT /api/v1/domains/registrations/{reg_id}/lock`: Lock or unlock a domain (https://www.coritan.com/docs/api/reference/client/domains/#op-put-api-v1-domains-registrations-reg-id-lock) # Transfer a domain to another registrar > Unlock a domain registered with Coritan and reveal its EPP code so another registrar can take it over. Source: https://www.coritan.com/docs/websites/domains/transfer-a-domain-away/ In the dashboard: - /dashboard/websites/…/settings: https://www.coritan.com/dashboard/websites Transfer a domain away when you want another registrar to hold it. You turn off the domain's transfer lock, reveal its EPP code on the **Settings** tab, and give the code to the new registrar, which starts the transfer. ## Before you begin - The registration must be `active` ([Statuses](/docs/websites/domains/#statuses)). - Decide where the domain's DNS will live. If Coritan hosts its zone, the zone keeps answering after the transfer for as long as the domain's nameservers point at Coritan. To move the DNS too, set up the records at the new provider and [change the nameservers](/docs/websites/domains/change-nameservers/) before you start. ## Get the EPP code 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites), open the domain and select the **Settings** tab. 2. In the **Domain options** card, turn **Transfer lock** off. 3. In the **EPP code** card, select **Reveal EPP code**. 4. Copy the code. The dashboard shows it once; select **Hide** when you have it. 5. At the new registrar, start the transfer and enter the code. > [!WARNING] > Anyone who holds the EPP code of an unlocked domain can transfer it. Give it only to the new registrar. If you do not go ahead with the transfer, turn **Transfer lock** back on. ## Result The new registrar asks the registry for the domain, and the transfer completes on the registry's schedule. If you lose the code before then, select **Reveal EPP code** again. ## Troubleshooting `Turn off the transfer lock before revealing the EPP code.` : The lock is on. Turn **Transfer lock** off in the **Domain options** card, then reveal the code. `Domain must be unlocked before requesting auth code` : The API's form of the message above. Send `PUT /api/v1/domains/registrations/{reg_id}/lock` with `{"enabled": false}` first. `Failed to get auth code: …` : The registry did not hand over the code. Try again later. ## Related - [Turn on WHOIS privacy and transfer lock](/docs/websites/domains/whois-privacy-and-transfer-lock/) - [Transfer a domain to Coritan](/docs/websites/domains/transfer-a-domain-to-coritan/) ## With the API Unlock the domain, then read the code: ```bash curl https://api.coritan.com/api/v1/domains/registrations/7/auth-code \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json {"domain": "example.com", "auth_code": "Xk7#pQ2m9L"} ``` The call answers `409` while the domain is locked, and `404` with `Registration not found` when the registration is not yours or not `active`. ## API - `GET /api/v1/domains/registrations/{reg_id}/auth-code`: Get the EPP/auth code for domain transfer out (https://www.coritan.com/docs/api/reference/client/domains/#op-get-api-v1-domains-registrations-reg-id-auth-code) # How DNS hosting works > How Coritan hosts a domain's DNS zone, from nameservers and records to TTLs, the SOA record and proxied records. Source: https://www.coritan.com/docs/websites/dns/ In the dashboard: - /dashboard/websites/…/dns: https://www.coritan.com/dashboard/websites A *DNS zone* holds the records for a domain and every name under it. When Coritan hosts a domain's zone, Coritan's nameservers answer every DNS query for it, and you manage its records on the domain's **DNS** tab or through the API. ## How a zone comes to exist A zone is created when you: - [add a domain you already own](/docs/websites/add-an-existing-domain/); - [register a domain](/docs/websites/domains/register-a-domain/) with **Issue an SSL certificate** or **Set up a proxy route** on; - [claim a free name](/docs/proxies/coritan-gg-names/) under `coritan.gg`. Every new zone is signed with DNSSEC and starts with an `NS` record at `@` for each Coritan nameserver, `ns1.coritan.com` to `ns8.coritan.com`. The zone answers for the domain once the domain's registrar lists those nameservers. ## Records and names A record has a name, a type, content and a TTL. Some types also take a priority, a weight or a port ([DNS record types](/docs/websites/dns/record-types/)). Write a record's name relative to the zone. In the zone `example.com`, `www` stands for `www.example.com`, `@` for `example.com` itself and `*` for any name that has no records of its own. Coritan stores the name as you type it, so `www.example.com` in that zone would mean `www.example.com.example.com`. Coritan's nameservers pick up a change as soon as you save it. A resolver that cached the old answer keeps it until the record's TTL runs out, so lower the TTL a while before a planned change. ## TTL The TTL says how long, in seconds, resolvers may keep an answer. It ranges from 60 seconds to 86,400 seconds (one day). The dashboard starts new records at 5 minutes, and the API at 300 seconds when you leave it out. ## The SOA record Every zone has a start of authority (SOA) record, which Coritan writes for you. It names `ns1.coritan.com` as the primary nameserver and the zone's contact address, `hostmaster@` the domain until you [change it](/docs/websites/settings/#dns-zone). Its *serial* goes up with every change, so secondary servers can tell when to fetch the zone again. The **DNS zone** card on the **Settings** tab shows the serial. ## Proxied records An `A` or `AAAA` record can be *proxied*. Coritan then answers the name with the addresses of its edge, with a short TTL of its own, instead of your address. Visitors' requests reach the edge, which applies the WAF and forwards them to your address over HTTP on port 80. Coritan also requests a certificate for the name, so visitors can use HTTPS. A proxied record's address must be public. To choose the port, the protocol or the error page, give the name a [web proxy](/docs/proxies/web-proxies/create-a-web-proxy/) of its own. [Web proxies and DNS records](/docs/proxies/web-proxies/#web-proxies-and-dns-records) explains how the two work together, and what deleting a proxied record does to a web proxy. ## How zones work with the rest of Coritan - A [load balancer](/docs/websites/load-balancing/) owns its hostname: the zone answers that name from the load balancer's pools, and refuses `A`, `AAAA` and `CNAME` records there. - A [web proxy](/docs/proxies/web-proxies/) serves a hostname once its DNS points at Coritan's edge. - [Certificates](/docs/websites/ssl/) validated by DNS write their `TXT` records into the zone for you. ## Limits - A zone holds up to 1,000 records, and an account up to 100 zones. - A zone keeps at least two `NS` records at `@`. - A `CNAME` cannot sit at `@`, and cannot share its name with any other record. ## Read a zone with the API ```bash curl https://api.coritan.com/api/v1/dns/zones/42 \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "id": 42, "domain": "example.com", "status": "active", "dnssec_enabled": true, "serial": 2026092507, "soa_email": "hostmaster", "record_count": 14, "created_at": "2026-09-16T10:04:00Z", "updated_at": "2026-09-25T08:12:00Z" } ``` `soa_email` holds only `hostmaster` for the default address at the zone itself. A zone that is not on your account answers `403` with `Access denied`, and one that does not exist answers `404` with `Zone not found`. ## Related - [Add, edit and delete DNS records](/docs/websites/dns/manage-dns-records/) - [Set up DNSSEC](/docs/websites/dns/dnssec/) - [Troubleshoot websites and DNS](/docs/websites/troubleshooting/) ## API - `GET /api/v1/dns/zones/{zone_id}`: Get zone (https://www.coritan.com/docs/api/reference/client/dns/#op-get-api-v1-dns-zones-zone-id) # Add, edit and delete DNS records > Create, change and delete a zone's DNS records on a website's DNS tab or through the API. Source: https://www.coritan.com/docs/websites/dns/manage-dns-records/ In the dashboard: - /dashboard/websites/…/dns: https://www.coritan.com/dashboard/websites A zone's records say where each name under the domain points: its website, its mail servers, the values other services ask you to publish. Manage them on the domain's **DNS** tab. Coritan's nameservers answer with a change as soon as you save it. ## Before you begin The domain's DNS must be hosted on Coritan. If the **DNS** tab says **No DNS zone for this domain**, [add the domain as an existing domain](/docs/websites/add-an-existing-domain/) first. ## Add a record 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites), open the domain and select the **DNS** tab. 2. Select **Add record…**. 3. Choose the record's **Type**: `A`, `AAAA`, `CNAME`, `MX`, `TXT`, `SRV`, `NS`, `CAA` or `DS`. 4. Enter the **Name** relative to the zone: `www` for `www.example.com`, or `@` for the domain itself. 5. Enter the **Content**, such as `203.0.113.10` for an `A` record. [DNS record types](/docs/websites/dns/record-types/) gives the format for each type. 6. For an `MX` or `SRV` record, enter its **Priority**. An `SRV` record also takes a **Weight** and a **Port**. 7. Choose the **TTL**, from **1 min** to **1 day**. 8. For an `A` or `AAAA` record, turn **Proxied** on to send the name's traffic through Coritan's edge ([Proxied records](/docs/websites/dns/#proxied-records)). 9. Optionally, add a **Comment** to remind you what the record is for. It shows under the name in the table. 10. Select **Add record**. ## Edit a record 1. On the **DNS** tab, open the record's actions menu and select **Edit record…**. 2. Change the **Content**, the **Priority**, the **TTL**, **Proxied** or the **Comment**. A record's type and name stay as they are; to change either, add a new record and delete the old one. While a record is proxied, a new **Content** also becomes the origin host of the name's web proxy. Turning **Proxied** off deletes that web proxy, as [deleting the record](#delete-a-record) does. 3. Select **Save record**. ## Delete a record > [!WARNING] > Deleting a proxied record, or turning **Proxied** off, also deletes the web proxy for its name, with its origin, switches, redirect rules and error page. This happens to a web proxy you created in Edge Proxy too ([Web proxies and DNS records](/docs/proxies/web-proxies/#web-proxies-and-dns-records)). 1. On the **DNS** tab, open the record's actions menu and select **Delete record…**. 2. Select **Delete record** to confirm. ## Find a record Select a type above the table, such as `MX`, to show only records of that type, and select it again to show them all. The search box matches names, types and content. **Reset filters** clears both. ## Result The dashboard confirms each change, such as `A record for www added.`, and the table shows the zone's records with their TTL and whether each one is **Proxied** or **DNS only**. Check a record from a terminal, straight from a Coritan nameserver: ```bash dig @ns1.coritan.com www.example.com A +short ``` ## Troubleshooting `TTL must be between 60 and 86400 seconds` : The TTL list also offers **Auto**, which the API refuses. Choose a time from **1 min** to **1 day**. `Invalid IPv4 address: …`, `Invalid IPv6 address: …` or `Invalid hostname: …` : The content does not fit the type. An `A` record takes an IPv4 address, an `AAAA` record an IPv6 address, and a `CNAME`, `MX`, `NS` or `SRV` record a hostname. `CNAME records cannot exist at zone apex` : A `CNAME` cannot sit at `@`. Use `A` and `AAAA` records for the domain itself. `CNAME cannot coexist with other records at the same name` or `Cannot add records that coexist with a CNAME at the same name` : A name with a `CNAME` has no other records. Delete the other records, or use a different name. `Hostname is owned by a load balancer; manage it under load balancers` : A load balancer answers for that name. Change its pools on the **Load balancing** tab ([Create a load balancer](/docs/websites/load-balancing/create-a-load-balancer/)). `Proxied origin targets a private or reserved network` : A proxied record must point at a public address. Turn **Proxied** off, or use your server's public address. `MX records require priority 0-65535` : Enter a **Priority** for the `MX` record. `SRV` records need a priority, a weight and a port in the same range. `Cannot delete this NS record. A zone needs at least 2 NS records.` : The zone keeps at least two `NS` records at `@`. `Record limit reached (1000)` : The zone holds as many records as it may. Delete records it no longer needs. ## Related - [How DNS hosting works](/docs/websites/dns/) - [DNS record types](/docs/websites/dns/record-types/) - [Import and export a zone file](/docs/websites/dns/import-and-export-a-zone-file/) ## With the API Add a record. The body takes `name`, `record_type`, `content` and `ttl` (300 when you leave it out), and, where the type needs them, `priority`, `weight` and `port`. `proxied` (for `A` and `AAAA` only) and `comment` are optional. ```bash curl -X POST https://api.coritan.com/api/v1/dns/zones/42/records \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "@", "record_type": "MX", "content": "mail.example.com", "priority": 10, "ttl": 3600}' ``` The answer is `201` with the record: ```json { "id": 311, "zone_id": 42, "name": "@", "record_type": "MX", "content": "mail.example.com", "ttl": 3600, "priority": 10, "weight": null, "port": null, "proxied": false, "comment": null, "created_at": "2026-09-25T09:30:00Z", "updated_at": "2026-09-25T09:30:00Z" } ``` List records with `GET /api/v1/dns/zones/42/records`, sorted by name and type. Filter with `record_type` and `name` (exact, such as `name=www`), and page with `page` and `per_page` (100 by default, up to 1,000). Read one record with `GET /api/v1/dns/zones/42/records/311`. Change a record with `PUT`, sending only the fields to change: `content`, `ttl`, `priority`, `weight`, `port`, `proxied` or `comment`. The type and the name cannot change. ```bash curl -X PUT https://api.coritan.com/api/v1/dns/zones/42/records/311 \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"ttl": 300}' ``` Delete a record with `DELETE /api/v1/dns/zones/42/records/311`, which answers `204`. ## API - `GET /api/v1/dns/zones/{zone_id}/records`: List records (https://www.coritan.com/docs/api/reference/client/dns/#op-get-api-v1-dns-zones-zone-id-records) - `POST /api/v1/dns/zones/{zone_id}/records`: Create record (https://www.coritan.com/docs/api/reference/client/dns/#op-post-api-v1-dns-zones-zone-id-records) - `GET /api/v1/dns/zones/{zone_id}/records/{record_id}`: Get record (https://www.coritan.com/docs/api/reference/client/dns/#op-get-api-v1-dns-zones-zone-id-records-record-id) - `PUT /api/v1/dns/zones/{zone_id}/records/{record_id}`: Update record (https://www.coritan.com/docs/api/reference/client/dns/#op-put-api-v1-dns-zones-zone-id-records-record-id) - `DELETE /api/v1/dns/zones/{zone_id}/records/{record_id}`: Delete record (https://www.coritan.com/docs/api/reference/client/dns/#op-delete-api-v1-dns-zones-zone-id-records-record-id) # DNS record types > The record types a Coritan DNS zone accepts, the content each one takes and the rules the API checks. Source: https://www.coritan.com/docs/websites/dns/record-types/ Every record has a name, a type, content and a TTL. The **DNS** tab's **Type** list offers the first nine types below. The API also accepts `PTR`, `SVCB` and `HTTPS`. ## Types | Type | Content | Other fields | Example content | | --- | --- | --- | --- | | `A` | An IPv4 address | Can be proxied | `203.0.113.10` | | `AAAA` | An IPv6 address | Can be proxied | `2001:db8::10` | | `CNAME` | The hostname this name is an alias of | None | `app.example.net` | | `MX` | The hostname of a mail server | Priority | `mail.example.com` | | `TXT` | Text, up to 2,048 characters | None | `v=spf1 include:_spf.example.com ~all` | | `SRV` | The hostname of the server that offers the service | Priority, weight and port | `play.example.com` | | `NS` | The hostname of a nameserver | None | `ns1.example.net` | | `CAA` | ` ""` | None | `0 issue "letsencrypt.org"` | | `DS` | ` ` | None | `2371 13 2 1F987CC6583E92DF0890718C42…` | | `PTR` | A hostname (API only) | None | `host.example.com` | | `SVCB` | ` [key=value ...]` (API only) | None | `1 . alpn="h3,h2"` | | `HTTPS` | ` [key=value ...]` (API only) | None | `1 . alpn="h3,h2"` | ## Rules for every record Name : Relative to the zone, up to 253 characters: `@` for the domain itself, `www` for `www.example.com`, `*` for any name without records of its own. Coritan lowercases it. TTL : From 60 to 86,400 seconds. The API uses 300 when you leave it out. Hostnames in content : For `CNAME`, `MX`, `NS`, `SRV` and `PTR`, Coritan lowercases the hostname and drops a trailing dot. Each label takes letters, digits, underscores and hyphens. Priority, weight and port : Whole numbers from 0 to 65,535. `MX` needs a priority. `SRV` needs all three. Comment : Optional text that only you see. It is not published in DNS. ## Rules for some types `A` and `AAAA` : Only these two can be proxied. A proxied record must point at a public address ([Proxied records](/docs/websites/dns/#proxied-records)). `CNAME` : Not at `@`, and never beside another record of any type at the same name. `TXT` : Type the text as it is; surrounding quotes are optional. Coritan splits text longer than 255 characters into the strings DNS carries. `SRV` : Put the service and protocol in the name, such as `_minecraft._tcp`, and the target hostname in the content. `NS` : At a name other than `@`, an `NS` record hands that name to other nameservers. The two or more `NS` records at `@` are the zone's own, and the zone keeps at least two. `CAA` : Lists the certificate authorities that may issue certificates for the name. Coritan requests certificates from Let's Encrypt and falls back to ZeroSSL and Google Trust Services. If you publish `CAA` records for a name that Coritan secures, add an `issue` record for each of `letsencrypt.org`, `sectigo.com` and `pki.goog` ([How SSL/TLS certificates work](/docs/websites/ssl/)). `DS` : Only at a delegated name, never at `@`. The digest is hexadecimal: 40 characters for digest type 1, 64 for types 2 and 3, and 96 for type 4. `SVCB` and `HTTPS` : Coritan checks the content with a DNS parser and refuses what it cannot read, with an example of the format in the error. ## Names a load balancer owns A name that a [load balancer](/docs/websites/load-balancing/) answers for takes no `A`, `AAAA` or `CNAME` records of its own. Change the load balancer instead, or delete it to manage the name with records again. ## Related - [Add, edit and delete DNS records](/docs/websites/dns/manage-dns-records/) - [How DNS hosting works](/docs/websites/dns/) # Import and export a zone file > Copy DNS records into a zone from a BIND zone file, or download a zone's records as one. Source: https://www.coritan.com/docs/websites/dns/import-and-export-a-zone-file/ In the dashboard: - /dashboard/websites/…/dns: https://www.coritan.com/dashboard/websites A zone file lists DNS records in the text format that BIND and most DNS providers use. Import one to copy many records into a Coritan zone at once, such as when you move a domain's DNS from another provider. Export one to keep a copy of a zone or to move its records elsewhere. ## Before you begin - The domain's DNS must be hosted on Coritan. If its **DNS** tab says **No DNS zone for this domain**, [add the domain as an existing domain](/docs/websites/add-an-existing-domain/) first. - To move records from another provider, download the zone file from that provider. ## Import records from a zone file 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites), open the domain and select the **DNS** tab. 2. Select **More DNS actions**, the three-dot button above the records table, then **Import zone file…**. 3. Paste the file into **Zone file**. Check it against [How the import reads a file](#how-the-import-reads-a-file) first. 4. Select **Import records**. ## How the import reads a file Coritan reads the file one line at a time and adds a record for each line it can read. Write each record on one line, in the order name, TTL, class, type and content. A line needs at least four parts separated by spaces, so keep the TTL or the `IN` on a record whose content is one word, such as an `A` record: ```dns @ 3600 IN A 203.0.113.10 www 3600 IN CNAME example.com. @ 3600 IN MX 10 mail.example.com. mail 3600 IN A 203.0.113.20 @ 3600 IN TXT "v=spf1 mx -all" _minecraft._tcp 3600 IN SRV 0 5 25565 play.example.com. ``` Name : `@` for the domain, a name relative to the zone such as `www`, or the full name with a trailing dot, such as `www.example.com.`. Every line starts with its name. Coritan does not carry a name over from the line above, as BIND does for a line that starts with a space. TTL : Optional. A line without one gets 300 seconds, whatever `$TTL` says. Coritan moves a TTL below 60 or above 86,400 seconds to the nearest of the two. Class : `IN`, which is optional. Type and content : The types and formats in [DNS record types](/docs/websites/dns/record-types/), checked by the same rules as the dashboard. Write every hostname in the content in full, such as `mail.example.com.`. Coritan reads a hostname without its trailing dot as complete, where BIND would add the zone's domain to it. A hostname cannot be `@`: name the domain in full. The import leaves out: - Blank lines, lines that start with `;`, and lines that start with `$`, such as `$ORIGIN` and `$TTL`. The records always go into the zone you import into. - The `SOA` record, which it reports as an unsupported type. The zone keeps its own. Before you import, remove comments at the ends of lines, and put a record that spans several lines in brackets onto one line. The import reads a comment as part of the content, and it cannot read a record over several lines. The import does not compare the file with the records the zone already has. It adds records beside them, so importing a file twice adds every record twice. It also accepts a `CNAME` beside other records at the same name, which the dashboard refuses. Imported `A` and `AAAA` records are **DNS only**; turn **Proxied** on for the ones that should go through Coritan's edge ([Add, edit and delete DNS records](/docs/websites/dns/manage-dns-records/#edit-a-record)). ## Export a zone file 1. On the domain's **DNS** tab, select **More DNS actions**, then **Export zone file**. 2. Your browser downloads the file, named after the domain, such as `example.com.zone`. The file starts with the zone's `SOA` record and lists every record, sorted by name and type: ```dns ; Zone file for example.com $ORIGIN example.com. $TTL 300 @ 300 IN SOA ns1.coritan.com. hostmaster.example.com. ( 2026092501 7200 3600 1209600 300 ) @ 86400 IN NS ns1.coritan.com @ 3600 IN MX 10 mail.example.com www 3600 IN CNAME example.com ``` - The file writes a hostname in content without its trailing dot. Coritan's import reads the file as it is, but before you load it into another provider, add the dot to each hostname: BIND and most providers would read `mail.example.com` as `mail.example.com.example.com`. - A proxied record is written with the address you entered, and nothing marks it as proxied. - Record comments are left out. ## Result After an import, the dashboard reports how many records it added and how many lines it could not read, such as `12 records imported; 2 lines skipped.` The new records appear in the table, and Coritan's nameservers answer with them straight away. After an export, the dashboard confirms `Zone file for example.com downloaded.` ## Troubleshooting `2 lines skipped` : The dashboard does not say which lines it could not read. Compare the table with the file, or import the file through the API, which lists each line with the reason. `Line 4: too few fields` : The line has fewer than four parts. Add a TTL or `IN`, such as `www 3600 IN A 203.0.113.10`. `Line 2: unsupported type SOA` : Coritan leaves out the `SOA` record, and every other record in the file still imports. A type Coritan does not host, such as `NAPTR`, is reported the same way. `Line 7: Invalid hostname: @` : A hostname in content cannot be `@`. Write the domain in full, such as `example.com.`. `Record limit reached (1000), import truncated` : A zone holds up to 1,000 records. The import added records up to the limit and stopped. Delete records the zone no longer needs, then import the rest. A record in the export has quotes inside its quotes : A `TXT` record that you imported with its quotes is written with a second pair around them, such as `"\"v=spf1 mx -all\""`. Delete the inner pair before you load the file elsewhere. ## Related - [DNS record types](/docs/websites/dns/record-types/) - [Add, edit and delete DNS records](/docs/websites/dns/manage-dns-records/) - [Add a domain you already own](/docs/websites/add-an-existing-domain/) ## With the API Import a zone file by sending its text as `zone_file`. In JSON, each line break is `\n`: ```bash curl -X POST https://api.coritan.com/api/v1/dns/zones/42/import \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"zone_file": "@ 3600 IN A 203.0.113.10\nwww 3600 IN CNAME example.com.\nbad line"}' ``` The answer counts the records added and lists each line the import could not read: ```json { "imported": 2, "errors": ["Line 3: too few fields"] } ``` Export a zone with `GET /api/v1/dns/zones/42/export`. The answer holds the file as one string: ```json { "zone_file": "; Zone file for example.com\n$ORIGIN example.com.\n$TTL 300\n…" } ``` ## API - `POST /api/v1/dns/zones/{zone_id}/import`: Import zone (https://www.coritan.com/docs/api/reference/client/dns/#op-post-api-v1-dns-zones-zone-id-import) - `GET /api/v1/dns/zones/{zone_id}/export`: Export zone (https://www.coritan.com/docs/api/reference/client/dns/#op-get-api-v1-dns-zones-zone-id-export) # Set up DNSSEC > Read a zone's DNSSEC status and DS records on its Settings tab, and publish the DS record at the domain's registrar. Source: https://www.coritan.com/docs/websites/dns/dnssec/ In the dashboard: - /dashboard/websites/…/settings: https://www.coritan.com/dashboard/websites DNSSEC signs a zone's answers so that resolvers can check they came from the zone's nameservers unchanged. Coritan signs every zone it hosts from the moment you add it. Resolvers check the signatures once the domain's registry publishes the zone's *DS records*, which you add at the domain's registrar. A free Coritan name, such as `survival-smp.coritan.gg`, needs no step from you: Coritan publishes its DS records in `coritan.gg` when you claim it. ## How Coritan signs a zone Coritan signs with algorithm 13, `ECDSAP256SHA256`, and two keys: *Key-signing key* (KSK) : Signs the zone's keys. The DS records are made from it, so they stay the same for as long as the zone keeps this key. *Zone-signing key* (ZSK) : Signs the zone's records. Coritan replaces it on a schedule, which needs no change at the registrar. The domain's **Settings** tab shows the zone's **Algorithm**, **KSK key tag**, **ZSK key tag** and **Last ZSK rotation** on the **DNSSEC** card. ## Before you begin - The domain's DNS must be hosted on Coritan, and the domain must use Coritan's nameservers at its registrar ([Add a domain you already own](/docs/websites/add-an-existing-domain/)). - You need to be able to change the domain's DNSSEC settings at its registrar. > [!IMPORTANT] > If the domain had DNSSEC at your previous DNS provider, delete that provider's DS records at the registrar before you move the domain to Coritan's nameservers. A DS record that does not match the zone's key makes validating resolvers refuse every answer for the domain. ## Publish the DS record at your registrar 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites), open the domain and select the **Settings** tab. 2. Check that the **DNSSEC** card shows the zone as enabled. 3. On the **DS records for the registrar** card, copy the records. Each one reads like this, with the key tag, the algorithm, the digest type and the digest after `DS`: ```dns example.com. IN DS 2371 13 2 1F987CC6583E92DF0890718C42… example.com. IN DS 2371 13 4 6D0C1BD5A4E1F2B7C3D8E9F0A1… ``` 4. At your registrar, open the domain's DNSSEC settings and add a DS record with the values from the first record: key tag `2371`, algorithm `13`, digest type `2` (SHA-256) and the digest. If the registrar accepts digest type `4` (SHA-384), you can add the second record as well. 5. Save the change at the registrar. For a domain registered with Coritan, the dashboard has no way to send DS records to the registry, so resolvers treat the zone as unsigned and do not check its signatures. [Contact support](/docs/support/) if you need DNSSEC validated for such a domain. ## Result Once the registry publishes the DS record, validating resolvers check every answer from the zone. The registry publishes changes on its own schedule. Check from a terminal that the DS record is live and that a validating resolver accepts the zone's answers: ```bash dig example.com DS +short dig @1.1.1.1 example.com A +dnssec ``` The second command's flags include `ad` when the resolver has checked the signatures. ## Troubleshooting `SERVFAIL` from validating resolvers : The DS record at the registrar does not match the zone's key. Compare it with the **DS records for the registrar** card, and delete any DS record left by a previous DNS provider. `Signing is off for this zone.` : The zone is not signed, so it has no DS records to publish. [Contact support](/docs/support/) to turn signing on. The DS records on the card have changed : The zone has a new key-signing key. Replace the DS records at your registrar with the new ones straight away, because validating resolvers refuse the zone's answers until the two match. ## Related - [How DNS hosting works](/docs/websites/dns/) - [Change a domain's nameservers](/docs/websites/domains/change-nameservers/) - [Delete a DNS zone](/docs/websites/dns/delete-a-dns-zone/) ## With the API Read a zone's DNSSEC status and DS records: ```bash curl https://api.coritan.com/api/v1/dns/zones/42/dnssec \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "enabled": true, "algorithm": "ECDSAP256SHA256", "ksk_key_tag": 2371, "zsk_key_tag": 40125, "ds_records": [ "example.com. IN DS 2371 13 2 1F987CC6583E92DF0890718C42…", "example.com. IN DS 2371 13 4 6D0C1BD5A4E1F2B7C3D8E9F0A1…" ], "zsk_rotated_at": "2026-09-20T03:00:00Z" } ``` `ds_records` is empty when `enabled` is `false`. `zsk_rotated_at` is `null` until Coritan first replaces the zone-signing key. ## API - `GET /api/v1/dns/zones/{zone_id}/dnssec`: Get DNSSEC info (https://www.coritan.com/docs/api/reference/client/dns/#op-get-api-v1-dns-zones-zone-id-dnssec) # Delete a DNS zone > Stop hosting a domain's DNS on Coritan and delete every record in its zone. Source: https://www.coritan.com/docs/websites/dns/delete-a-dns-zone/ In the dashboard: - /dashboard/websites/…/settings: https://www.coritan.com/dashboard/websites Delete a domain's zone when you no longer want Coritan's nameservers to answer for it, such as after you move its DNS to another provider. Coritan stops answering for the domain as soon as the zone is gone. > [!CAUTION] > Deleting a zone cannot be undone. Coritan deletes every record in it, its load balancers and origin pools and its query statistics. It also deletes the web proxy of every name that has a **Proxied** record, including web proxies you created. Export a zone file first to keep a copy of the records. ## Before you begin - [Export the zone file](/docs/websites/dns/import-and-export-a-zone-file/#export-a-zone-file) to keep a copy of the records. - If the domain should keep working, set up its records at the new DNS provider and change the nameservers at the registrar first. Keep the Coritan zone until resolvers use the new nameservers, which can take as long as the TTL on the registry's `NS` records. - If the registry has the zone's DS records, delete them at the registrar before you change the nameservers ([Set up DNSSEC](/docs/websites/dns/dnssec/)). Otherwise validating resolvers refuse the new provider's answers. - For a domain registered with Coritan, [change its nameservers](/docs/websites/domains/change-nameservers/) first. The domain keeps Coritan's nameservers after the zone is gone, and nothing answers for it. - For a free Coritan name, such as `survival-smp.coritan.gg`, the **Settings** tab offers **Release name…** instead ([Claim a free coritan.gg name](/docs/proxies/coritan-gg-names/)). ## Delete the zone 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites), open the domain and select the **Settings** tab. 2. On the **Delete the DNS zone** card, select **Delete zone…**. 3. Type the domain, such as `example.com`, to confirm. 4. Select **Delete zone**. ## Result The dashboard confirms `DNS zone for example.com deleted.` and Coritan's nameservers stop answering for the domain. A domain registered with Coritan stays on the **Websites** list, marked as registered here with its DNS elsewhere. Any other domain leaves the list. Coritan does not delete the domain's registration, its certificates, its mail domains or a web proxy whose name had no **Proxied** record. They need the domain's DNS to answer for its names, here or at another provider. ## Troubleshooting `Zone not found` : The zone was already deleted. Refresh the page. The domain stopped resolving : The registrar still lists Coritan's nameservers. Change them to the new provider's nameservers, or add the domain to Coritan again ([Add a domain you already own](/docs/websites/add-an-existing-domain/)). The domain fails DNSSEC checks after you add it again : A new zone gets new DNSSEC keys. Replace the DS records at the registrar with the ones on the new zone's **DS records for the registrar** card. ## Related - [Import and export a zone file](/docs/websites/dns/import-and-export-a-zone-file/) - [Change a domain's nameservers](/docs/websites/domains/change-nameservers/) - [How DNS hosting works](/docs/websites/dns/) ## With the API Delete a zone by its ID. The answer is `204` with no body: ```bash curl -X DELETE https://api.coritan.com/api/v1/dns/zones/42 \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` Deleting the zone of a free Coritan name through this call also releases the name. ## API - `DELETE /api/v1/dns/zones/{zone_id}`: Delete zone (https://www.coritan.com/docs/api/reference/client/dns/#op-delete-api-v1-dns-zones-zone-id) # How DNS load balancing works > How a load balancer answers DNS queries from pools of origins, with health checks, location steering and failover. Source: https://www.coritan.com/docs/websites/load-balancing/ In the dashboard: - /dashboard/websites/…/load-balancing: https://www.coritan.com/dashboard/websites A *load balancer* answers `A` and `AAAA` queries for one hostname in a zone, such as `api.example.com`, with the address of one of your servers. Coritan picks the server for each query from *origin pools*, skips servers that fail their health checks, and can send clients in different places to different pools. You manage both on a domain's **Load balancing** tab. ## Terms *Origin* : A server that can answer for the hostname, given by its public IPv4 or IPv6 address. An IPv4 origin answers `A` queries and an IPv6 origin answers `AAAA` queries. *Origin pool* : A named group of origins in one zone, such as the web servers in one data centre. The pool's algorithm decides which of its origins answers. *Load balancer* : A hostname in the zone and the pools that answer for it, in order, with the steering that reorders them. *Steering* : How the load balancer orders its pools for a query: as listed, at random, by where the client is, or by distance. *Fallback pool* : The pool that answers when every pool in the load balancer's order has no healthy origin. ## How a query is answered 1. Steering puts the load balancer's pools in order for this query. 2. Coritan goes down the order and skips disabled pools and pools with no healthy origin of the query's type. 3. The first pool left chooses one origin, and the answer carries that origin's address with the load balancer's TTL. 4. If no pool in the order can answer, the fallback pool tries. 5. If that fails too, Coritan answers with one of the enabled origins anyway, so the name keeps resolving while it has any. Each answer holds one address. Visitors then connect straight to that origin: their traffic does not pass through Coritan's edge, its WAF or its certificates. For that, put a [web proxy](/docs/proxies/web-proxies/) in front of the hostname instead. ## Choosing an origin inside a pool **Weighted round-robin** : Picks a healthy origin at random, in proportion to its **Weight** from 1 to 100. An origin with weight 20 gets about twice the queries of one with weight 10. **Failover (by priority)** : Answers with the healthy origin that has the lowest **Priority** number. The others answer only while it is unhealthy. The load balancer's **Session affinity** can change both. With **By client IP**, the same resolver gets the same origin while that origin stays healthy. Coritan sees the address of the visitor's DNS resolver, so every visitor behind one resolver gets the same origin. In a **Failover (by priority)** pool, **By client IP** spreads resolvers over all the healthy origins, whatever their priority. ## Health checks An origin can have a **Health check** of **HTTP**, **HTTPS** or **TCP connect**. Coritan checks every enabled origin that has one once a minute, and a check fails when the origin does not answer within its **Timeout (s)**. - An **HTTP** or **HTTPS** check asks for the check's **Path** at the origin's address and passes on a status from 200 to 399. The request names the IP address as its host, so the origin must answer a request that does not name your domain. - An **HTTPS** check also verifies the origin's certificate against its IP address. A certificate that names only your domain fails it, so use **HTTP** or **TCP connect** for such an origin. - A **TCP connect** check passes when the port accepts a connection. An origin shows `healthy`, `unhealthy` or `unknown`. A new origin is `unknown` until its first check, and Coritan treats it as healthy until then. An origin with no check is always treated as healthy. **Run health checks** in a pool's menu checks its origins straight away. ## Steering **Off** : The load balancer tries its **Default pools** in the order you numbered them. **Random** : It puts the default pools in a random order for each query. **Geographic** : It matches the client's location against rules **By PoP**, then **By country**, then **By region**, and uses the pools of the first rule that matches. A client that no rule matches gets the default pools. **Proximity** : It ranks the pools by the distance from the client to each pool's **Primary location**. A pool without one comes last. **Geographic** and **Proximity** need to know where the client is. **Locate the client by** sets how: **Answering PoP** : The client is where the Coritan nameserver that answered the query is: that PoP, its country and its region. **Resolver IP** : Coritan looks up the country of the resolver's IP address, and the region that country is in. **By PoP** rules do not match, because the resolver has no PoP. **Proximity** has no coordinates to measure from, so it keeps the pools in their listed order. When the address has no known country, Coritan uses the answering PoP instead. ## Load balancers and DNS records A load balancer owns its hostname for `A`, `AAAA` and `CNAME` records. You cannot create one on a name that already has such records, and the zone refuses them at the name while the load balancer exists, even when it is off. While it is off, the name gets no `A` or `AAAA` answers. Other types at the name, such as `TXT` or `MX`, are unaffected. When you delete the load balancer, you can add records at the name again. ## Limits | Limit | Value | | --- | --- | | Origin pools per zone | 50 | | Origins per pool | 20 | | Load balancers per zone | 50 | | Pool entries per load balancer, across its default pools and every rule | 200 | | TTL of a load balancer's answers | 5–3,600 seconds | ## Related - [Create an origin pool](/docs/websites/load-balancing/create-an-origin-pool/) - [Create a load balancer](/docs/websites/load-balancing/create-a-load-balancer/) - [Load balancing reference](/docs/websites/load-balancing/reference/) # Create an origin pool > Group the servers that answer for a hostname into a pool, with weights, priorities and health checks. Source: https://www.coritan.com/docs/websites/load-balancing/create-an-origin-pool/ In the dashboard: - /dashboard/websites/…/load-balancing: https://www.coritan.com/dashboard/websites An origin pool groups the servers that can answer for a hostname, such as the web servers in one data centre, with a health check on each. Create one pool per group of servers, then a [load balancer](/docs/websites/load-balancing/create-a-load-balancer/) that answers a hostname from your pools. ## Before you begin - The domain's DNS must be hosted on Coritan. If the **Load balancing** tab says **No DNS zone**, [add the domain](/docs/websites/add-an-existing-domain/) first. - Each origin needs a public IPv4 or IPv6 address. Coritan refuses private and reserved addresses. ## Create a pool 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites), open the domain and select the **Load balancing** tab. 2. Under **Origin pools**, select **New pool…**. 3. Enter a **Name** that no other pool in the zone has, such as `eu-origins`. A **Description** is optional. 4. Choose the **Algorithm**: **Weighted round-robin** shares queries by weight, and **Failover (by priority)** answers from the origin with the lowest priority number ([Choosing an origin inside a pool](/docs/websites/load-balancing/#choosing-an-origin-inside-a-pool)). 5. If a load balancer will use proximity steering, choose the pool's **Primary location**, the Coritan location nearest its origins. 6. Fill in the first origin under **Origins**: - **Address**: the server's public IP address, such as `203.0.113.10`. - **Weight**: from 1 to 100, for **Weighted round-robin**. - **Priority**: 0 or more, for **Failover (by priority)**. The lowest number answers first. 7. Choose the origin's **Health check** and fill in its fields: - **HTTP** or **HTTPS**: the **Path** to ask for, such as `/healthz`, and the **Port** if it is not 80 for HTTP or 443 for HTTPS. - **TCP connect**: the **Port** to connect to. Without one, Coritan connects to port 80. - **Timeout (s)**: how long the origin has to answer. 8. Select **Add origin** for each other server, and fill it in the same way. Coritan ignores an origin card with no address. 9. Select **Create pool**. Coritan checks every origin that has a health check once a minute, whatever its **Every (s)** says. The pool's **TTL (s)** and **Session affinity** do not change a load balancer's answers, which use the load balancer's own ([How DNS load balancing works](/docs/websites/load-balancing/#choosing-an-origin-inside-a-pool)). ## Check the origins now Open the pool's actions menu and select **Run health checks**. Coritan checks every origin in the pool that has a health check, and the badge beside each origin shows its new status. ## Change, turn off or delete a pool - To change a pool or its origins, open the pool's actions menu and select **Edit pool…**, make the change, then select **Save pool**. To remove an origin, select the × on its card before you save. - To stop an origin answering without removing it, turn off **Enabled** on its card. The scheduled checks skip a disabled origin. - To stop every load balancer answering from a pool, turn off its **Enabled** switch in the table, or **Pool enabled** in the pool's dialog. The load balancers skip it until you turn it back on. - To delete a pool, open its actions menu, select **Delete pool…**, type the pool's name and select **Delete pool**. First remove it from every load balancer that lists it: the dialog names them, and Coritan refuses to delete a pool that one still uses. > [!CAUTION] > Deleting a pool deletes its origins and their health checks. It cannot be undone. ## Result The dashboard confirms `Pool "eu-origins" created.`, and the pool appears under **Origin pools** with its origins and a **Health** badge such as `2 of 2 healthy`. The badge counts the enabled origins that are healthy, or not yet checked. ## Troubleshooting `Pool saved, but could not save 1 origin.` : Coritan saved the pool but refused an origin. The dialog stays open and says why for each one; correct the origin and select **Save pool** again. `health-check target targets a private or reserved network` : The origin's address is private or reserved, such as `10.0.0.5`. Use the server's public address. `A members require an IPv4 address` or `AAAA members require an IPv6 address` : An origin keeps the address family it was created with. To move it from IPv4 to IPv6, remove it and add a new origin. `Pool name already exists in this zone` : Another pool in the zone has that name. Choose a different one. `Pool is referenced by a load balancer; remove it from maps/default/fallback first` : A load balancer still lists the pool as a default pool, the fallback pool or in a rule. Edit each load balancer the dialog names, remove the pool, then delete it. `Origin pool limit reached (50)` or `LB member limit reached (20)` : A zone holds up to 50 pools, and a pool up to 20 origins. Delete what you no longer need, or split the origins across pools. An origin stays `unhealthy` : Read why in the origin's `last_error` from the API, such as `HTTP 404` ([With the API](#with-the-api)). The check must get a status from 200 to 399 for its path at the origin's IP address. An **HTTPS** check fails on a certificate that does not include the IP address, so use **HTTP** or **TCP connect** for such an origin. ## Related - [How DNS load balancing works](/docs/websites/load-balancing/) - [Create a load balancer](/docs/websites/load-balancing/create-a-load-balancer/) - [Load balancing reference](/docs/websites/load-balancing/reference/) ## With the API Create a pool in zone `42`. `algorithm` is `weighted_rr` or `failover`, and `primary_location_code` is a location code such as the ones the **Primary location** list shows: ```bash curl -X POST https://api.coritan.com/api/v1/dns/zones/42/pools \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "eu-origins", "description": "Web servers in Frankfurt", "algorithm": "weighted_rr"}' ``` The answer is `201` with the pool, which has no origins yet: ```json { "id": 7, "zone_id": 42, "name": "eu-origins", "description": "Web servers in Frankfurt", "hostname": null, "algorithm": "weighted_rr", "ttl": 30, "session_affinity": "none", "primary_location_code": null, "enabled": true, "members": [], "healthy_members": 0, "total_members": 0, "created_at": "2026-09-25T09:30:00Z", "updated_at": "2026-09-25T09:30:00Z" } ``` Add an origin to the pool. Set `address_type` to `AAAA` for an IPv6 address; it is `A` when you leave it out. `health_mode` is `none`, `http`, `https` or `tcp`: ```bash curl -X POST https://api.coritan.com/api/v1/dns/zones/42/pools/7/members \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"address": "203.0.113.10", "weight": 10, "health_mode": "http", "health_path": "/healthz", "health_timeout_s": 5}' ``` The answer is `201` with the origin. Its `health_status` is `unknown` until the first check, or `healthy` when it has no check. `last_check_at` and `last_error` record the latest check. Run a check now with `POST /api/v1/dns/zones/42/pools/7/members/31/health-check`, which answers with the origin and its new `health_status`. The other calls: | Call | What it does | | --- | --- | | `GET /api/v1/dns/zones/42/pools` | Lists the zone's pools by name, each with its origins. | | `GET /api/v1/dns/zones/42/pools/7` | Reads one pool. | | `PATCH /api/v1/dns/zones/42/pools/7` | Changes the fields you send: `name`, `description`, `algorithm`, `ttl`, `session_affinity`, `primary_location_code` or `enabled`. | | `DELETE /api/v1/dns/zones/42/pools/7` | Deletes the pool and its origins, and answers `204`. It answers `409` while a load balancer uses the pool. | | `PATCH /api/v1/dns/zones/42/pools/7/members/31` | Changes the origin fields you send. `address_type` cannot change. | | `DELETE /api/v1/dns/zones/42/pools/7/members/31` | Removes the origin, and answers `204`. | The same calls also work under `/api/v1/dns/zones/42/lb-pools`, for older clients. A pool created with a `hostname` also gets a load balancer for that name, with steering off and the pool as its default and fallback pool. ## API - `GET /api/v1/dns/zones/{zone_id}/pools`: List origin pools (https://www.coritan.com/docs/api/reference/client/dns/#op-get-api-v1-dns-zones-zone-id-pools) - `POST /api/v1/dns/zones/{zone_id}/pools`: Create origin pool (https://www.coritan.com/docs/api/reference/client/dns/#op-post-api-v1-dns-zones-zone-id-pools) - `GET /api/v1/dns/zones/{zone_id}/pools/{pool_id}`: Get origin pool (https://www.coritan.com/docs/api/reference/client/dns/#op-get-api-v1-dns-zones-zone-id-pools-pool-id) - `PATCH /api/v1/dns/zones/{zone_id}/pools/{pool_id}`: Update origin pool (https://www.coritan.com/docs/api/reference/client/dns/#op-patch-api-v1-dns-zones-zone-id-pools-pool-id) - `DELETE /api/v1/dns/zones/{zone_id}/pools/{pool_id}`: Delete origin pool (https://www.coritan.com/docs/api/reference/client/dns/#op-delete-api-v1-dns-zones-zone-id-pools-pool-id) - `POST /api/v1/dns/zones/{zone_id}/pools/{pool_id}/members`: Create origin member (https://www.coritan.com/docs/api/reference/client/dns/#op-post-api-v1-dns-zones-zone-id-pools-pool-id-members) - `PATCH /api/v1/dns/zones/{zone_id}/pools/{pool_id}/members/{member_id}`: Update origin member (https://www.coritan.com/docs/api/reference/client/dns/#op-patch-api-v1-dns-zones-zone-id-pools-pool-id-members-member-id) - `DELETE /api/v1/dns/zones/{zone_id}/pools/{pool_id}/members/{member_id}`: Delete origin member (https://www.coritan.com/docs/api/reference/client/dns/#op-delete-api-v1-dns-zones-zone-id-pools-pool-id-members-member-id) - `POST /api/v1/dns/zones/{zone_id}/pools/{pool_id}/members/{member_id}/health-check`: Health check origin member (https://www.coritan.com/docs/api/reference/client/dns/#op-post-api-v1-dns-zones-zone-id-pools-pool-id-members-member-id-health-check) - `GET /api/v1/dns/zones/{zone_id}/lb-pools`: List LB pools (https://www.coritan.com/docs/api/reference/client/dns/#op-get-api-v1-dns-zones-zone-id-lb-pools) - `POST /api/v1/dns/zones/{zone_id}/lb-pools`: Create LB pool (https://www.coritan.com/docs/api/reference/client/dns/#op-post-api-v1-dns-zones-zone-id-lb-pools) - `GET /api/v1/dns/zones/{zone_id}/lb-pools/{pool_id}`: Get LB pool (https://www.coritan.com/docs/api/reference/client/dns/#op-get-api-v1-dns-zones-zone-id-lb-pools-pool-id) - `PATCH /api/v1/dns/zones/{zone_id}/lb-pools/{pool_id}`: Update LB pool (https://www.coritan.com/docs/api/reference/client/dns/#op-patch-api-v1-dns-zones-zone-id-lb-pools-pool-id) - `DELETE /api/v1/dns/zones/{zone_id}/lb-pools/{pool_id}`: Delete LB pool (https://www.coritan.com/docs/api/reference/client/dns/#op-delete-api-v1-dns-zones-zone-id-lb-pools-pool-id) - `POST /api/v1/dns/zones/{zone_id}/lb-pools/{pool_id}/members`: Create LB member (https://www.coritan.com/docs/api/reference/client/dns/#op-post-api-v1-dns-zones-zone-id-lb-pools-pool-id-members) - `PATCH /api/v1/dns/zones/{zone_id}/lb-pools/{pool_id}/members/{member_id}`: Update LB member (https://www.coritan.com/docs/api/reference/client/dns/#op-patch-api-v1-dns-zones-zone-id-lb-pools-pool-id-members-member-id) - `DELETE /api/v1/dns/zones/{zone_id}/lb-pools/{pool_id}/members/{member_id}`: Delete LB member (https://www.coritan.com/docs/api/reference/client/dns/#op-delete-api-v1-dns-zones-zone-id-lb-pools-pool-id-members-member-id) - `POST /api/v1/dns/zones/{zone_id}/lb-pools/{pool_id}/members/{member_id}/health-check`: Health check LB member (https://www.coritan.com/docs/api/reference/client/dns/#op-post-api-v1-dns-zones-zone-id-lb-pools-pool-id-members-member-id-health-check) # Create a load balancer > Answer a hostname from your origin pools, steered by the client's location, with a fallback pool when the others are down. Source: https://www.coritan.com/docs/websites/load-balancing/create-a-load-balancer/ In the dashboard: - /dashboard/websites/…/load-balancing: https://www.coritan.com/dashboard/websites A load balancer answers `A` and `AAAA` queries for one hostname in the zone from your origin pools. Create one when a name should resolve to whichever of your servers is healthy, or to the servers nearest each client. ## Before you begin - Create at least one [origin pool](/docs/websites/load-balancing/create-an-origin-pool/) in the zone. **New load balancer…** is available once the zone has one. - The hostname must have no `A`, `AAAA` or `CNAME` records. If it has some, create the pools first, then delete the records on the **DNS** tab just before you create the load balancer, so the name is without answers for as short a time as possible. ## Create the load balancer 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites), open the domain and select the **Load balancing** tab. 2. Under **Load balancers**, select **New load balancer…**. 3. Enter a **Name** for your own use, such as `api-global`. 4. Enter the **Hostname**: a name under the zone, such as `api` or `eu.api`, or `@` for the domain itself. The hint under the field shows the full name the load balancer answers for. 5. Set the **TTL (s)**, from 5 to 3,600 seconds. Resolvers ask again after this time, so a short TTL moves clients off an unhealthy origin sooner. 6. Choose the **Session affinity**: **None**, or **By client IP** to give each resolver the same origin while it stays healthy. 7. Choose the **Steering**, and for **Geographic** or **Proximity**, how to **Locate the client by** ([Steering](/docs/websites/load-balancing/#steering)). 8. Under **Default pools**, select the pools in the order to try them. Each selected pool shows its place in the order; select it again to take it out. 9. Optionally, choose a **Fallback pool** to answer when no pool above has a healthy origin. The load balancer needs at least one default pool or a fallback pool. 10. For **Geographic** steering, add rules under **By region**, **By country** and **By PoP**. For each, select **Add rule**, choose the region or PoP, or type a two-letter country code such as `DE`, then select the rule's pools in order. 11. Select **Create load balancer**. A **Geographic** load balancer uses the most specific rule that matches: PoP, then country, then region. A client that matches no rule gets the default pools. ## Preview the answer Check what a client in a given place would get before you rely on the load balancer. 1. Open the load balancer's actions menu and select **Preview answers**. 2. Choose a Coritan PoP under **From PoP**, or leave it at **Any (use resolver IP)** and enter a resolver's address under **Resolver IP**. 3. Choose the **Type**, `A` or `AAAA`, and select **Preview answer**. The panel shows the **Answer**, which pool and rule chose it under **Chosen by**, the **Pool order** steering produced, and where Coritan placed the client under **Client seen as**. The preview uses the origins' current health. Select **Hide preview** in the menu to close it. ## Change, turn off or delete a load balancer - To change it, open its actions menu, select **Edit load balancer…**, make the change and select **Save load balancer**. - To stop it answering, turn off its **Enabled** switch in the table, or **Load balancer enabled** in its dialog. While it is off, the name gets no `A` or `AAAA` answers. - To delete it, open its actions menu, select **Delete load balancer…**, type its full hostname, such as `api.example.com`, and select **Delete load balancer**. > [!WARNING] > A deleted load balancer stops answering at once, and the name has no `A` or `AAAA` answers until you add records for it on the **DNS** tab. Its pools and their origins stay. ## Result The dashboard confirms `Load balancer api.example.com created.`, and the load balancer appears under **Load balancers** with its **Steering** and **Pools**. Ask a Coritan nameserver from a terminal; each answer holds one origin's address: ```bash dig @ns1.coritan.com api.example.com A +short ``` ## Troubleshooting `Hostname already has A/AAAA/CNAME records; remove them before creating a load balancer` : Delete the name's `A`, `AAAA` and `CNAME` records on the **DNS** tab, then create the load balancer. `A load balancer already owns this hostname` : Another load balancer in the zone answers for that name. Edit it instead, or choose another hostname. `Choose at least one default pool or a fallback pool.` : Select a pool under **Default pools**, or choose a **Fallback pool**. `Enter a label under the zone, such as api or eu.api, or @ for the apex.` : Enter the hostname relative to the zone, without the domain: `api` for `api.example.com`. `Add a pool to the country rule for DE.` : Every rule needs at least one pool. Select one in the rule, or remove the rule. `Load balancer limit reached (50)` or `Steering entry limit reached (200)` : A zone holds up to 50 load balancers, and a load balancer up to 200 pool entries across its default pools and rules. The preview shows `No answer · no members` : No pool in the order has an enabled origin of the type you asked for. An `AAAA` query needs IPv6 origins. The preview shows `No answer · load balancer disabled` : The load balancer is off. Turn it on in the table. The page warns `Proximity steering needs a location on every pool` : A pool without a **Primary location** cannot be ranked by distance, so it answers only when no closer pool can. Set one on each pool. ## Related - [How DNS load balancing works](/docs/websites/load-balancing/) - [Create an origin pool](/docs/websites/load-balancing/create-an-origin-pool/) - [Load balancing reference](/docs/websites/load-balancing/reference/) ## With the API Create a load balancer in zone `42` from pools `7` and `8`, with pool `9` as the fallback. `hostname` is relative to the zone, and the default pools are tried in the order you list them: ```bash curl -X POST https://api.coritan.com/api/v1/dns/zones/42/load-balancers \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "api-global", "hostname": "api", "ttl": 30, "steering_policy": "geo", "location_strategy": "pop", "default_pools": [7, 8], "fallback_pool_id": 9, "region_pools": {"WEU": [7], "ENAM": [8]}, "country_pools": {"DE": [7]} }' ``` The answer is `201` with the load balancer: ```json { "id": 5, "zone_id": 42, "name": "api-global", "hostname": "api", "enabled": true, "ttl": 30, "session_affinity": "none", "steering_policy": "geo", "location_strategy": "pop", "fallback_pool_id": 9, "default_pools": [7, 8], "location_pools": {}, "country_pools": {"DE": [7]}, "region_pools": {"WEU": [7], "ENAM": [8]}, "created_at": "2026-09-25T09:30:00Z", "updated_at": "2026-09-25T09:30:00Z" } ``` Region codes are listed in the [reference](/docs/websites/load-balancing/reference/#region-codes). Country codes are two-letter ISO codes, and PoP codes are the location codes the **From PoP** list shows. List a zone's load balancers with `GET /api/v1/dns/zones/42/load-balancers`, sorted by hostname, and read one with `GET /api/v1/dns/zones/42/load-balancers/5`. `PATCH` changes the fields you send; a list or map you send, such as `default_pools` or `country_pools`, replaces the old one whole. `DELETE` answers `204`. Preview an answer. Send `from_location` with a PoP code, or `from_ip` with a resolver's address, and `qtype`: ```bash curl -X POST https://api.coritan.com/api/v1/dns/zones/42/load-balancers/5/preview \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"qtype": "A", "from_ip": "198.51.100.53"}' ``` ```json { "load_balancer_id": 5, "hostname": "api", "context": {"location_code": null, "country_code": "DE", "region_code": "WEU", "latitude": null, "longitude": null, "source": "resolver_ip"}, "pool_order": [7], "cascade_hit": "country", "selected_pool_id": 7, "member": {"id": 31, "pool_id": 7, "address": "203.0.113.10", "address_type": "A", "weight": 10, "priority": 0, "enabled": true, "health_status": "healthy"}, "servfail": false, "reason": "ok" } ``` `member` is the origin chosen, with the fields of an origin in its pool (the example leaves some out), or `null` when there is no answer. The [reference](/docs/websites/load-balancing/reference/#preview-results) lists the values of `cascade_hit` and `reason`. ## API - `GET /api/v1/dns/zones/{zone_id}/load-balancers`: List load balancers (https://www.coritan.com/docs/api/reference/client/dns/#op-get-api-v1-dns-zones-zone-id-load-balancers) - `POST /api/v1/dns/zones/{zone_id}/load-balancers`: Create load balancer (https://www.coritan.com/docs/api/reference/client/dns/#op-post-api-v1-dns-zones-zone-id-load-balancers) - `GET /api/v1/dns/zones/{zone_id}/load-balancers/{lb_id}`: Get load balancer (https://www.coritan.com/docs/api/reference/client/dns/#op-get-api-v1-dns-zones-zone-id-load-balancers-lb-id) - `PATCH /api/v1/dns/zones/{zone_id}/load-balancers/{lb_id}`: Update load balancer (https://www.coritan.com/docs/api/reference/client/dns/#op-patch-api-v1-dns-zones-zone-id-load-balancers-lb-id) - `DELETE /api/v1/dns/zones/{zone_id}/load-balancers/{lb_id}`: Delete load balancer (https://www.coritan.com/docs/api/reference/client/dns/#op-delete-api-v1-dns-zones-zone-id-load-balancers-lb-id) - `POST /api/v1/dns/zones/{zone_id}/load-balancers/{lb_id}/preview`: Preview load balancer (https://www.coritan.com/docs/api/reference/client/dns/#op-post-api-v1-dns-zones-zone-id-load-balancers-lb-id-preview) # Load balancing reference > Fields, limits, region codes and error messages for origin pools and load balancers. Source: https://www.coritan.com/docs/websites/load-balancing/reference/ The fields below appear in the dashboard's dialogs on a domain's **Load balancing** tab and in the API's request and response bodies. The limits are in [How DNS load balancing works](/docs/websites/load-balancing/#limits). ## Origin pool fields | Dashboard | API field | Values | | --- | --- | --- | | **Name** | `name` | 1–100 characters, unique in the zone | | **Description** | `description` | Optional text | | **Algorithm** | `algorithm` | `weighted_rr` (**Weighted round-robin**, the default) or `failover` (**Failover (by priority)**) | | **TTL (s)** | `ttl` | 5–3,600 seconds, 30 by default. Load balancers answer with their own TTL. | | **Session affinity** | `session_affinity` | `none` or `client_ip`. Load balancers use their own. | | **Primary location** | `primary_location_code` | A Coritan location code, or `null` for **Not set** | | **Pool enabled** | `enabled` | `true` by default. A disabled pool is skipped. | A pool in the API also has `id`, `zone_id`, `members` (its origins), `healthy_members`, `total_members`, `created_at`, `updated_at` and `hostname`, which older clients send to create a load balancer with the pool. ## Origin fields | Dashboard | API field | Values | | --- | --- | --- | | **Address** | `address` | A public IPv4 or IPv6 address | | None; the dashboard reads it from the address | `address_type` | `A` (the default) or `AAAA`. It cannot change after the origin is created. | | **Weight** | `weight` | 1–100, 1 by default | | **Priority** | `priority` | 0 or more, 0 by default. The lowest number answers first. | | **Enabled** | `enabled` | `true` by default | | **Health check** | `health_mode` | `none` (**None**, the default), `http` (**HTTP**), `https` (**HTTPS**) or `tcp` (**TCP connect**) | | **Path** | `health_path` | `/` by default | | **Port** | `health_port` | 1–65,535. Empty means 443 for `https` and 80 otherwise. | | **Every (s)** | `health_interval_s` | 30 by default. Coritan checks once a minute whatever it says. | | **Timeout (s)** | `health_timeout_s` | 5 by default | The API also returns `id`, `pool_id`, `health_status`, `last_check_at`, `last_error` (why the last check failed, such as `HTTP 503`), `created_at` and `updated_at`. ## Health statuses `healthy` : The last check passed, or the origin has no check. `unhealthy` : The last check failed. Load balancers skip the origin while another origin can answer. `unknown` : The origin has not been checked yet. Load balancers treat it as healthy. ## Load balancer fields | Dashboard | API field | Values | | --- | --- | --- | | **Name** | `name` | 1–100 characters | | **Hostname** | `hostname` | `@` for the domain, or a name under the zone such as `api` | | **TTL (s)** | `ttl` | 5–3,600 seconds, 30 by default | | **Session affinity** | `session_affinity` | `none` (**None**, the default) or `client_ip` (**By client IP**) | | **Steering** | `steering_policy` | `off` (**Off**, the default), `random` (**Random**), `geo` (**Geographic**) or `proximity` (**Proximity**) | | **Locate the client by** | `location_strategy` | `pop` (**Answering PoP**, the default) or `resolver_ip` (**Resolver IP**) | | **Default pools** | `default_pools` | Pool IDs, in the order to try them | | **Fallback pool** | `fallback_pool_id` | A pool ID, or `null` for **None** | | **By region** | `region_pools` | Region codes, each with a list of pool IDs, such as `{"WEU": [7]}` | | **By country** | `country_pools` | Two-letter country codes, each with a list of pool IDs, such as `{"DE": [7]}` | | **By PoP** | `location_pools` | PoP codes, each with a list of pool IDs | | **Load balancer enabled** | `enabled` | `true` by default | The dashboard saves the rules only for **Geographic** steering. Saving a load balancer with any other **Steering** deletes its rules. ## Region codes | Code | Region | | --- | --- | | `WNAM` | Western North America | | `ENAM` | Eastern North America | | `WEU` | Western Europe | | `EEU` | Eastern Europe | | `SEAS` | Southeast Asia | | `NEAS` | Northeast Asia | | `SAS` | Southern Asia | | `ME` | Middle East | | `AF` | Africa | | `OC` | Oceania | | `SAM` | South America | When Coritan places a client by its resolver's IP address, it puts the United States in `ENAM` and Canada in `WNAM`. To split North America by coast, use **By PoP** rules with **Answering PoP**. List the regions with the countries in each: ```bash curl https://api.coritan.com/api/v1/dns/lb-regions \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json [ {"code": "WNAM", "name": "Western North America", "sort_order": 10, "countries": ["US", "CA", "MX"]}, {"code": "ENAM", "name": "Eastern North America", "sort_order": 20, "countries": ["US", "CA"]} ] ``` ## Preview results **Preview answers** in the dashboard and `POST /api/v1/dns/zones/{zone_id}/load-balancers/{lb_id}/preview` in the API report how the answer was chosen. | `cascade_hit` | **Chosen by** shows | Meaning | | --- | --- | --- | | `location` | `PoP rule` | A **By PoP** rule matched. | | `country` | `Country rule` | A **By country** rule matched. | | `region` | `Region rule` | A **By region** rule matched. | | `default` | `Default pools` | The default pools answered. | | `fallback` | `Fallback pool` | The fallback pool answered. | | `proximity` | `Nearest pool` | Proximity steering ranked the pools. | | `serve_last` | `Last healthy answer` | No pool had a healthy origin, and Coritan answered with an enabled origin anyway. | `reason` is one of: `ok` : A healthy origin answered. `all_unhealthy_serve_last` : No origin was healthy, and an enabled origin answered anyway. `all_unhealthy` : No origin was healthy, and the answer is `SERVFAIL` (`servfail` is `true`). `no_members` : No pool in the order has an enabled origin of the type asked for. `load_balancer_disabled` : The load balancer is off. `context.source` says how the preview placed the client: `pop` for a PoP you chose, `resolver_ip` for an address you entered, and `override` when you gave neither. ## Errors | Message | Status | Cause | | --- | --- | --- | | `Origin pool not found`, `LB member not found`, `Load balancer not found` | 404 | The ID is not in this zone. | | `Origin pool limit reached (50)`, `LB member limit reached (20)`, `Load balancer limit reached (50)` | 403 | The zone or pool is full. | | `Steering entry limit reached (200)` | 403 | The load balancer lists more than 200 pool entries across its default pools and rules. | | `Pool name already exists in this zone`, `Pool name already exists` | 409 | Another pool in the zone has that name. | | `A load balancer already owns this hostname` | 409 | Another load balancer answers for the name. | | `Hostname already has A/AAAA/CNAME records; remove them before creating a load balancer` | 409 | The name has records a load balancer would replace. | | `Pool is referenced by a load balancer; remove it from maps/default/fallback first` | 409 | A load balancer still lists the pool. | | `Invalid IP address: …` | 422 | The origin's address is not an IP address. | | `A members require an IPv4 address`, `AAAA members require an IPv6 address` | 422 | The address does not match `address_type`. | | `health-check target targets a private or reserved network` | 422 | The origin's address is private or reserved. | | `Unknown region code: …` | 422 | A `region_pools` key is not in the region list. | | `Unknown pool ids for this zone: […]` | 422 | A pool ID in the request belongs to no pool in this zone. | A value outside its range, such as a `weight` of `0`, gets a `422` validation error that names the field. ## API - `GET /api/v1/dns/lb-regions`: List DNS LB regions (https://www.coritan.com/docs/api/reference/client/dns/#op-get-api-v1-dns-lb-regions) # How SSL/TLS certificates work > How Coritan issues, validates and renews free SSL/TLS certificates for your domains. Source: https://www.coritan.com/docs/websites/ssl/ In the dashboard: - /dashboard/websites/…/ssl: https://www.coritan.com/dashboard/websites Coritan gets SSL/TLS certificates from public certificate authorities and installs them at the edge, so visitors reach your web proxies over HTTPS. A certificate you request covers one name, such as `example.com` or `www.example.com`. Coritan does not charge for certificates, and it renews them before they expire. ## Certificate authorities Coritan orders certificates with the ACME protocol. It asks Let's Encrypt first, then ZeroSSL, then Google Trust Services. It moves to the next authority when one refuses the order or has no allowance left for the domain that week. It does not move on when validation fails, because every authority checks the same proof. The **Issuer** row on the **Certificate** card names the authority that issued a certificate. If the domain publishes `CAA` records, they must allow all three authorities ([DNS record types](/docs/websites/dns/record-types/#rules-for-some-types)). ## Validation Before an authority issues a certificate, it checks that you control the name. The **SSL** tab offers three ways to prove it: **Coritan DNS (automatic)** : Coritan adds a `TXT` record named `_acme-challenge` to the zone, waits until its nameservers serve it, and deletes it after validation. You do nothing. It needs a zone that Coritan hosts, with the domain's nameservers set to Coritan's. This is DNS-01 validation. **Manual DNS TXT record** : For a domain whose DNS is at another provider. You add the `_acme-challenge` `TXT` record there, with a value that Coritan shows you. This is DNS-01 validation too. **HTTP validation** : The name must resolve to the Coritan edge. The authority fetches a file from `http://example.com/.well-known/acme-challenge/`, and the edge answers it. This is HTTP-01 validation. A fourth choice, **Let Coritan choose**, picks HTTP validation when the name has a web proxy, Coritan DNS when Coritan hosts the zone, and HTTP validation otherwise. When HTTP validation fails and Coritan hosts the zone, Coritan tries again with Coritan DNS. The **Challenge** row on the **Certificate** card shows how a certificate was validated: `DNS-01`, `DNS-01, manual` or `HTTP-01`. ## Where certificates are served The edge serves a certificate for a name that has a [web proxy](/docs/proxies/web-proxies/). A new web proxy requests its own certificate while **Request a certificate automatically** is on, which it is by default. A certificate issued for a name with no web proxy stays on your account, and Coritan uses it when a web proxy for the name requests a certificate. Without a certificate, the edge answers HTTPS for the name with a certificate for another name, and browsers warn visitors. When you claim a free coritan.gg name, Coritan requests a wildcard certificate for it, such as `*.survival-smp.coritan.gg`. The wildcard covers the name and every name directly under it, and the **SSL** tab says so when it is the certificate in use. You cannot request a wildcard certificate for any other name. When the one for a coritan.gg name was not requested, or you need it again, [request it again through the API](/docs/proxies/coritan-gg-names/#request-the-wildcard-certificate-again). ## Certificate statuses `pending` : Coritan has ordered the certificate, and the authority is validating the name. `active` : Issued and in use. The **Expires** row shows when it runs out. `failed` : The last order or renewal failed, and **The last request failed** on the **Certificate** card gives the reason. A certificate that failed to renew stays in use at the edge until it expires. `expired` : Past its expiry date, or replaced by a newer certificate for the same name. `revoked` : You revoked it, and Coritan no longer serves it. ## Renewal Coritan checks certificates every day and renews an `active` certificate once it has 30 days or less left. It renews with the method the certificate was validated with, so that proof must still work: the zone must still be at Coritan for Coritan DNS, and the name must still resolve to the edge for HTTP validation. A certificate validated with **Manual DNS TXT record** needs a new `TXT` value at each renewal, and Coritan cannot add it for you, so its automatic renewal fails. For a certificate that renews on its own, move the domain's DNS to Coritan or use HTTP validation. [Renew or revoke a certificate](/docs/websites/ssl/renew-or-revoke-a-certificate/) covers renewing by hand. ## Issuance limit Certificate authorities limit how many certificates they issue for one registered domain in a week, so Coritan limits it too. The count covers every certificate for names under the registered domain, such as `example.com` for `www.example.com`. The **Issuance limit** card on the **SSL** tab shows how many certificates were issued in the last seven days, out of the limit. At the limit, Coritan refuses new orders until older certificates leave the seven-day window. ## Read certificates with the API List the certificates for the zones and registered domains on your account, newest first. `per_page` takes up to 100 and defaults to 25: ```bash curl "https://api.coritan.com/api/v1/ssl/certificates?page=1&per_page=25" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` The answer is `{"certificates": [...], "total": 3}`, and the list includes certificates in every status. Read the certificate that secures one name, with the issuance count for its registered domain: ```bash curl https://api.coritan.com/api/v1/ssl/certificates/example.com/status \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "domain": "example.com", "has_certificate": true, "certificate": { "id": 118, "domain": "example.com", "route_id": 31, "zone_id": 42, "status": "active", "issuer": "letsencrypt", "challenge_method": "dns01", "issued_at": "2026-09-01T02:14:09", "expires_at": "2026-11-30T01:14:08", "last_renewal_at": "2026-09-01T02:14:09", "renewal_attempts": 0, "error_message": null, "created_at": "2026-09-01T02:12:51", "san_domains": [], "serial": null, "fingerprint_sha256": null, "requested_provider": null, "failover_from": [] }, "rate_limit": { "registered_domain": "example.com", "issued_last_7_days": 1, "limit": 45, "remaining": 44, "allowed": true }, "wildcard": false } ``` `certificate` is the newest certificate for the name. When the name has none, it is a wildcard that covers the name and `wildcard` is `true`, or `null` when nothing covers it. `has_certificate` is `true` only when that certificate is `active`. `issuer` is `letsencrypt`, `zerossl` or `google_trust_services`, and `challenge_method` is `dns01`, `dns01_manual` or `http01`. `renewal_attempts` counts the failed attempts recorded for the certificate. This endpoint and the list leave `san_domains`, `serial`, `fingerprint_sha256`, `requested_provider` and `failover_from` empty. ## Related - [Issue an SSL/TLS certificate](/docs/websites/ssl/issue-a-certificate/) - [Renew or revoke a certificate](/docs/websites/ssl/renew-or-revoke-a-certificate/) - [How web proxies work](/docs/proxies/web-proxies/) ## API - `GET /api/v1/ssl/certificates`: List SSL certificates for the authenticated user (https://www.coritan.com/docs/api/reference/client/ssl/#op-get-api-v1-ssl-certificates) - `GET /api/v1/ssl/certificates/{domain}/status`: Get SSL certificate status for a domain (https://www.coritan.com/docs/api/reference/client/ssl/#op-get-api-v1-ssl-certificates-domain-status) # Issue an SSL/TLS certificate > Prove you control a domain by DNS or HTTP and issue a free certificate for it on a website's SSL tab. Source: https://www.coritan.com/docs/websites/ssl/issue-a-certificate/ In the dashboard: - /dashboard/websites/…/ssl: https://www.coritan.com/dashboard/websites Issue a certificate for a website on its **SSL** tab. You choose how to prove that you control the name, and Coritan installs the certificate on the name's web proxy once the certificate authority issues it. A new web proxy requests its own certificate, so you need these steps for a name that has none, or one whose certificate failed or expired. [How SSL/TLS certificates work](/docs/websites/ssl/) explains the validation methods. ## Before you begin - The domain must be on your [Websites](https://www.coritan.com/dashboard/websites) list: a zone that Coritan hosts, or a domain registered with Coritan. - If the domain publishes `CAA` records, they must allow Let's Encrypt, ZeroSSL and Google Trust Services ([DNS record types](/docs/websites/dns/record-types/#rules-for-some-types)). - The **SSL** tab shows the **Request a certificate** card when the name has no certificate, or when its certificate is `failed` or `expired`. - The **SSL** tab covers the website's own name, such as `example.com`. For another name with a web proxy, such as `www.example.com`, [request the certificate from the web proxy](#request-a-certificate-for-a-web-proxy). ## Issue a certificate with Coritan DNS Use this method when Coritan hosts the zone and the domain uses Coritan's nameservers. The dashboard selects it for you in that case. 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites), open the domain and select the **SSL** tab. 2. On the **Request a certificate** card, select **Coritan DNS (automatic)**. 3. In **Install on proxy route**, keep the website's web proxy, or choose **Zone only** to keep the certificate without installing it. 4. Select **Start validation**. The card shows **Nothing to do**. 5. Select **Issue certificate**. ## Issue a certificate with a TXT record at another DNS provider Use this method when the domain's DNS is at another provider and the name does not resolve to the Coritan edge. You create the record first with any value, then replace the value once Coritan has ordered the certificate. 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites), open the domain and select the **SSL** tab. 2. On the **Request a certificate** card, select **Manual DNS TXT record**, then **Start validation**. The card lists a `TXT` record named `_acme-challenge.example.com` with a TTL of `60`, and its value is still empty. 3. At your DNS provider, create a `TXT` record at `_acme-challenge.example.com` with any value, such as `pending`. 4. Wait until the badge reads **TXT record found, value differs**, or select **Check now**. 5. Select **Complete validation**. The card shows **Issuing the certificate**. 6. After a few seconds, reload the page. The card shows the `TXT` record again, now with its value. 7. At your DNS provider, replace the record's value with the one shown. Do it within five minutes of step 5, because Coritan checks for the value for five minutes. When public resolvers return the new value, the badge reads **TXT record found** and the certificate authority issues the certificate. You do not need to select **Complete validation** again. Delete the `TXT` record once the certificate is `active`. ## Issue a certificate with HTTP validation Use this method when the name resolves to the Coritan edge, or when you can point it there. 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites), open the domain and select the **SSL** tab. 2. On the **Request a certificate** card, select **HTTP validation**, then **Start validation**. The card lists an `A` record for the name with the edge address as its value, and the addresses the name resolves to now. 3. If the badge reads **Not pointed at the edge**, set the name's `A` record to the address shown at the DNS provider that answers for the domain. 4. Wait until the badge reads **Ready**. The card checks every few seconds, and **Check now** checks at once. 5. Select **Complete validation**. It stays unavailable until the name resolves to the edge address. Keep the name pointed at the edge after the certificate is issued. Renewal validates the same way. ## Request a certificate for a web proxy A web proxy's **Overview** tab has its own **Certificate** card. Use it for a name that has no **SSL** tab, such as `www.example.com`. 1. In the dashboard, go to [Edge Proxy](https://www.coritan.com/dashboard/proxies) and open the web proxy. 2. On the **Overview** tab, find the **Certificate** card and select **Request certificate**. The button appears when the proxy has no `active` certificate and none is being issued. The card shows **Issuance in progress** while the certificate authority validates the name. Coritan uses a certificate that already covers the name when there is one. Otherwise it validates over HTTP, so the name must resolve to the edge, and it falls back to Coritan DNS when Coritan hosts the zone. ## Result The dashboard confirms `Certificate issuance for example.com started.` The **Certificate** card shows `pending` while the certificate authority validates the name, then `active` with the **Issuer** and **Expires** rows. The web proxy for the name serves the certificate. With **Zone only**, the certificate waits on your account until a web proxy for the name requests one. ## Troubleshooting `This domain has reached its weekly issuance limit.` : The registered domain is at its issuance limit, and **Start validation** is unavailable. Wait until the count on the **Issuance limit** card falls. `Rate limit: 45/45 certificates issued by all authorities for example.com in the past 7 days` : The same limit, reported when you start validation. Wait until older certificates leave the seven-day window. `You must own this domain via registration, DNS zone, or proxy route` : The name is not on your account. Add its zone first ([Add a domain you already own](/docs/websites/add-an-existing-domain/)). `No TXT record found at _acme-challenge.example.com. Please add the record and try again.` : Public resolvers do not return a `TXT` record at that name yet. Check the record's name at your DNS provider, wait for it to appear, and select **Complete validation** again. `Domain does not resolve to this server (203.0.113.10). Current IPs: none. Update your A record and try again.` : The name does not resolve to the edge address. Set its `A` record to the address in the message and try again once the change has spread. `Certificate issuance already in progress` : Coritan is already issuing a certificate for the name. Wait for the **Certificate** card to show the result. **The last request failed** mentions `DNS verification failed` : Coritan did not see the `TXT` value within five minutes. Start again with **Manual DNS TXT record**, and replace the value as soon as it appears. **The last request failed** mentions `HTTP verification failed` : Coritan could not fetch the validation file from the name over HTTP. Check that the name resolves only to the edge address and that port 80 reaches the edge. Then start again. **The last request failed** mentions `CAA` : A `CAA` record for the domain does not allow the certificate authorities that Coritan uses. Add an `issue` record for each one, then start again. ## Related - [How SSL/TLS certificates work](/docs/websites/ssl/) - [Renew or revoke a certificate](/docs/websites/ssl/renew-or-revoke-a-certificate/) - [Create a web proxy](/docs/proxies/web-proxies/create-a-web-proxy/) ## With the API The **SSL** tab runs a challenge in three calls: start it, read its status, then complete it. Start a challenge with a `method` of `auto` (the default), `dns_automatic`, `dns_manual` or `http`: ```bash curl -X POST https://api.coritan.com/api/v1/ssl/challenges/start \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"domain": "example.com", "method": "http"}' ``` The answer is `201` with what to do next. `auto` answers with the method it picked: ```json { "domain": "example.com", "method": "http", "status": "not_pointed", "txt_name": null, "txt_value": null, "expected_ip": "203.0.113.10", "current_ips": ["198.51.100.7"], "is_pointed": false, "instructions": "Point your domain's A record to 203.0.113.10, then call the /complete endpoint. Current resolution: 198.51.100.7", "created_at": "2026-09-25T10:00:00Z", "expires_at": "2026-09-25T11:00:00Z" } ``` `status` is `ready` or `not_pointed` for `http`, and `ready` for `dns_automatic`. For `dns_manual` it starts as `awaiting_order`, with `txt_name` set and `txt_value` still `null`. Read it again with the status call. For a manual challenge the status is `awaiting_record` while there is no record, `txt_found` while the record holds another value, and `verified` once it holds `txt_value`. Without a manual challenge in progress, the call reports whether the name points at the edge: ```bash curl https://api.coritan.com/api/v1/ssl/challenges/example.com/status \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` Complete an `http` or `dns_manual` challenge to order the certificate. The answer is `202`: ```bash curl -X POST https://api.coritan.com/api/v1/ssl/challenges/example.com/complete \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "message": "Certificate issuance started via HTTP verification", "job_id": 5812, "domain": "example.com" } ``` For `dns_manual` the message is `Certificate issuance started via manual DNS verification`. Read the status again after a few seconds: `txt_value` now holds the value to publish. A `dns_automatic` challenge has nothing to complete. Order the certificate directly instead, which is what **Issue certificate** does. `challenge_method` is `dns01` or `http01`, and leaving it out lets Coritan choose. `route_id` installs the certificate on the web proxy with that ID, for the same name: ```bash curl -X POST https://api.coritan.com/api/v1/ssl/certificates \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"domain": "example.com", "challenge_method": "dns01", "route_id": 31}' ``` ```json { "message": "Certificate issuance queued", "job_id": 5813, "domain": "example.com" } ``` The call answers `403` with `You must own the domain's DNS zone or domain registration` for a name outside your zones and registered domains, and `404` with `Route not found or access denied` for a `route_id` that is not yours. Poll [the certificate status](/docs/websites/ssl/#read-certificates-with-the-api) until `certificate.status` is no longer `pending`. ## API - `POST /api/v1/ssl/certificates`: Request SSL certificate issuance for a domain owned by the user (https://www.coritan.com/docs/api/reference/client/ssl/#op-post-api-v1-ssl-certificates) - `POST /api/v1/ssl/challenges/start`: Start an SSL challenge for a domain (https://www.coritan.com/docs/api/reference/client/ssl/#op-post-api-v1-ssl-challenges-start) - `POST /api/v1/ssl/challenges/{domain}/complete`: Finalize verification and trigger certificate issuance (https://www.coritan.com/docs/api/reference/client/ssl/#op-post-api-v1-ssl-challenges-domain-complete) - `GET /api/v1/ssl/challenges/{domain}/status`: Check the current verification status for a domain (https://www.coritan.com/docs/api/reference/client/ssl/#op-get-api-v1-ssl-challenges-domain-status) # Renew or revoke a certificate > Renew an SSL/TLS certificate before it expires, or revoke one you no longer want served. Source: https://www.coritan.com/docs/websites/ssl/renew-or-revoke-a-certificate/ In the dashboard: - /dashboard/websites/…/ssl: https://www.coritan.com/dashboard/websites Coritan renews certificates on its own once they have 30 days or less left ([How SSL/TLS certificates work](/docs/websites/ssl/#renewal)). Renew one yourself when it is close to expiry and has not renewed, or when you want a new one early. Revoke a certificate to stop Coritan serving it. ## Before you begin - The certificate must be `active`. The **Renewal** card, with **Renew now** and **Revoke certificate…**, appears only for an `active` certificate. - A renewal validates the same way the certificate was first validated. For Coritan DNS the zone must still be at Coritan, and for HTTP validation the name must still resolve to the edge. ## Renew a certificate 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites), open the domain and select the **SSL** tab. 2. On the **Renewal** card, select **Renew now**. For a name without an **SSL** tab, such as `www.example.com`, go to [Edge Proxy](https://www.coritan.com/dashboard/proxies), open its web proxy and select **Renew now** on the **Certificate** card of the **Overview** tab. ## Revoke a certificate > [!WARNING] > Revoking removes the certificate from the name's web proxy. Visitors get a certificate error over HTTPS until a new certificate is in place. 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites), open the domain and select the **SSL** tab. 2. On the **Renewal** card, open the **More certificate actions** menu and select **Revoke certificate…**. 3. Type the domain, such as `example.com`, to confirm. 4. Select **Revoke certificate**. ## Result After **Renew now**, the dashboard confirms `Renewal of the certificate for example.com started.` Once the certificate authority issues the new certificate, the **Expires** row moves forward, **Last renewal** shows the date, and the web proxy serves the new certificate. After you revoke a certificate, the dashboard confirms `Certificate for example.com revoked.` and the certificate shows `revoked`. Coritan stops serving it, but it does not ask the certificate authority to revoke it. To serve HTTPS again, [request a certificate from the web proxy](/docs/websites/ssl/issue-a-certificate/#request-a-certificate-for-a-web-proxy). ## Troubleshooting The certificate shows `failed` after a renewal : The renewal did not validate. The previous certificate stays in use until its **Expires** date. Read the reason under **The last request failed**, fix it, and [issue a new certificate](/docs/websites/ssl/issue-a-certificate/) before that date. A certificate validated with a manual TXT record did not renew : Each renewal needs a new `TXT` value, which Coritan cannot add at another DNS provider, so the renewal fails and the certificate shows `failed`. Run [the manual DNS validation](/docs/websites/ssl/issue-a-certificate/#issue-a-certificate-with-a-txt-record-at-another-dns-provider) again before the **Expires** date. To stop this happening, move the domain's DNS to Coritan or use HTTP validation. A certificate is close to its **Expires** date and has not renewed : Select **Renew now**. If the renewal fails, the **Certificate** card gives the reason. The **SSL** tab offers no way to request a certificate after you revoked one : The tab keeps showing the `revoked` certificate. Select **Request certificate** on the web proxy's **Overview** tab in Edge Proxy, or order one with the API. `No active certificate found for this domain` : Only an `active` certificate can be renewed. Issue a new certificate instead. `Renewal already in progress` : Coritan is already renewing the certificate. Wait for the **Certificate** card to update. ## Related - [How SSL/TLS certificates work](/docs/websites/ssl/) - [Issue an SSL/TLS certificate](/docs/websites/ssl/issue-a-certificate/) ## With the API Renew an `active` certificate by its name. The answer is `202`: ```bash curl -X POST https://api.coritan.com/api/v1/ssl/certificates/example.com/renew \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "message": "Certificate renewal queued", "job_id": 5814, "domain": "example.com" } ``` Revoke it. The answer is `200` with `{"message": "Certificate revoked", "domain": "example.com"}`, or `404` with `No active certificate found` when the name has no `active` certificate: ```bash curl -X DELETE https://api.coritan.com/api/v1/ssl/certificates/example.com \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` To serve HTTPS again, order a new certificate with `POST /api/v1/ssl/certificates` ([Issue an SSL/TLS certificate](/docs/websites/ssl/issue-a-certificate/#with-the-api)). ## API - `POST /api/v1/ssl/certificates/{domain}/renew`: Request certificate renewal for a domain (https://www.coritan.com/docs/api/reference/client/ssl/#op-post-api-v1-ssl-certificates-domain-renew) - `DELETE /api/v1/ssl/certificates/{domain}`: Revoke/deactivate an SSL certificate for a domain (https://www.coritan.com/docs/api/reference/client/ssl/#op-delete-api-v1-ssl-certificates-domain) # Manage a website's web proxy > Put a domain behind Coritan's edge on its Proxy tab, then change its web proxy, point it at one of your servers or delete it. Source: https://www.coritan.com/docs/websites/proxy/ In the dashboard: - /dashboard/websites/…/proxy: https://www.coritan.com/dashboard/websites A website's **Proxy** tab manages the web proxy for the domain itself, such as `example.com`. A *web proxy* receives the domain's traffic at Coritan's edge, applies HTTPS and the WAF, and forwards each request to your origin ([How web proxies work](/docs/proxies/web-proxies/)). Web proxies for names under the domain, such as `www.example.com`, are in [Edge Proxy](https://www.coritan.com/dashboard/proxies/web) and on the domain's **WAF** tab. ## Before you begin - The domain is on your [Websites](https://www.coritan.com/dashboard/websites) list, registered with Coritan or with its DNS zone hosted here. - To send the domain to one of your servers, you need a server on your account with an address and a port. - Visitors reach the web proxy only when the domain resolves to Coritan's edge. When Coritan hosts the zone and the domain has no `A`, `AAAA` or `CNAME` record yet, the web proxy writes a proxied `A` or `AAAA` record for it. If the domain already has an `A` or `AAAA` record, turn **Proxied** on for it on the **DNS** tab. - When another provider hosts the DNS, point the domain at the edge there. The **SSL** tab shows the address when you choose **HTTP validation** ([Issue a certificate with HTTP validation](/docs/websites/ssl/issue-a-certificate/#issue-a-certificate-with-http-validation)). ## Send the domain to one of your servers 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites), open the domain and select the **Proxy** tab. 2. On the **Connect to one of your servers** card, choose the **Server**. The card shows the address and port that requests will go to. 3. Leave **Request a certificate automatically** on, so Coritan issues a certificate for the domain. 4. Select **Connect domain**. The web proxy starts with **Force HTTPS**, **WebSockets** and **Web application firewall** on, and connects to the server over plain HTTP. If you have no server yet, the card shows **Order server** instead. ## Forward the domain to any origin 1. On the **Proxy** tab, select **New route…**. 2. Enter the **Origin host**, an IP address or a hostname such as `origin.example.net`, and its **Port**. 3. Choose the **Origin protocol**, and turn on **Connect to the origin over TLS** if the origin serves HTTPS on that port. 4. Choose the switches and, under **Advanced options**, any path, header or extra origin settings. [Create a web proxy](/docs/proxies/web-proxies/create-a-web-proxy/) describes each field. 5. Select **Create route**. ## Change the web proxy The **Origin** card lists the web proxy's **Origins**, **Origin protocol**, **Path**, **Custom headers**, **Limits** and **Status**. - To change them, select **Edit route…**, make the changes and select **Save changes**. [Change a web proxy's origin](/docs/proxies/web-proxies/change-the-origin/) explains each field. - The **Switches** card turns **Force HTTPS**, **WebSockets**, **Web application firewall**, **TLS to origin** and **PROXY protocol to origin** on or off. Each switch saves as soon as you change it. - The **Certificate** card has **Request certificate** and **Open SSL**. The **SSL** tab shows the certificate the edge serves for the domain ([How SSL/TLS certificates work](/docs/websites/ssl/)). - **Open in Edge Proxy** opens the web proxy's own page, with its **Redirects** and **Error page** tabs. On a narrow screen, open the web proxy from [Edge Proxy](https://www.coritan.com/dashboard/proxies/web) instead. ## Repoint the domain at one of your servers 1. On the **Repoint at one of your servers** card, choose the **Server**. 2. Leave **Request a certificate automatically** on to request a new certificate at the same time. 3. Select **Repoint domain**. Repointing replaces only the origin host and port with the server's address and port. The switches, path settings, headers and any additional origins stay as they are. ## Delete the web proxy > [!WARNING] > Deleting the web proxy deletes its settings, its redirect rules and its custom error page. Visitors who reach the edge for the domain see an error page until the domain has a web proxy again or its DNS points elsewhere. 1. On the **Delete this proxy route** card, select **Delete route…**. 2. Type the domain, such as `example.com`, to confirm. 3. Select **Delete route**. The domain's DNS records stay as they are. A proxied record keeps sending visitors to the edge, so turn **Proxied** off for the record on the **DNS** tab to send them straight to your address ([Add, edit and delete DNS records](/docs/websites/dns/manage-dns-records/#edit-a-record)). The certificate stays on the **SSL** tab. ## Result Each change shows a confirmation, such as `Route for example.com created.`, `example.com now points at 203.0.113.10:8080.` or `Proxy route for example.com deleted.`. The tab then shows the web proxy's origin, certificate and switches, or the two ways to create one. ## Troubleshooting `A route for this domain already exists` : The domain already has a web proxy that this tab does not show, such as the one a proxied `A` or `AAAA` record creates. Turn **Proxied** off for the domain's record on the **DNS** tab, create the web proxy, then turn **Proxied** on again. If the domain has no proxied record, [contact support](/docs/support/). `Domain route owned by another user` : Another account has a web proxy for the domain. [Contact support](/docs/support/). `Server has no allocated IP/port. Ensure the server has an active allocation.` : The server has no address and port yet. Wait until its setup finishes, or choose another server. `Unable to resolve hostname: origin.example.net` : The origin host does not resolve in public DNS. Check its spelling, or enter the origin's IP address. `host targets a private or reserved network` or `resolved address targets a private or reserved network` : The edge only forwards to public addresses. Enter the origin's public address. The form refuses the origin host : The **Origin host** takes a hostname or an IP address only, with no `https://` and no port. [Create a web proxy](/docs/proxies/web-proxies/create-a-web-proxy/#troubleshooting) lists the form's messages. Visitors get a certificate warning : The certificate is not issued yet, or its issuance failed. Open the **SSL** tab to see why ([Issue a certificate](/docs/websites/ssl/issue-a-certificate/#troubleshooting)). Visitors see an error page from Coritan : The edge cannot reach your origin. [Troubleshoot proxies and join addresses](/docs/proxies/troubleshooting/) covers each error. ## Related - [How web proxies work](/docs/proxies/web-proxies/) - [Create a web proxy](/docs/proxies/web-proxies/create-a-web-proxy/) - [Protect a website with the WAF](/docs/websites/waf/) ## With the API The **Proxy** tab uses the web proxy API, which [Create a web proxy](/docs/proxies/web-proxies/create-a-web-proxy/#with-the-api) and [Change a web proxy's origin](/docs/proxies/web-proxies/change-the-origin/#with-the-api) describe. To find the web proxy for a domain, list your web proxies and pick the one whose `domain` matches exactly: ```bash curl "https://api.coritan.com/api/v1/proxy/routes?per_page=200" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` # Protect a website with the WAF > Turn the web application firewall, HTTPS redirects and development mode on or off for each web proxy under a domain. Source: https://www.coritan.com/docs/websites/waf/ In the dashboard: - /dashboard/websites/…/waf: https://www.coritan.com/dashboard/websites - /security/waf: https://www.coritan.com/security/waf A web application firewall (WAF) checks each request to a website before the request reaches your origin, and refuses requests that look like attacks. Coritan's WAF runs at the edge, and each web proxy has it on or off. A website's **WAF** tab lists every web proxy under the domain, such as `example.com` and `www.example.com`, with its WAF, **Force HTTPS** and **Development mode** switches. [The web application firewall](/docs/proxies/web-proxies/#the-web-application-firewall) explains what the WAF checks and what visitors see when it refuses a request. ## Before you begin - Each name you want to protect needs a web proxy ([Manage a website's web proxy](/docs/websites/proxy/) or [Create a web proxy](/docs/proxies/web-proxies/create-a-web-proxy/)). - The WAF only sees traffic that comes through Coritan's edge, so each name must resolve to the edge ([Web proxies and DNS records](/docs/proxies/web-proxies/#web-proxies-and-dns-records)). ## Turn the WAF on or off 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites), open the domain and select the **WAF** tab. 2. Find the web proxy on the **Routes under this domain** card. The **Route** column shows its name, and where it forwards to under the name. 3. Switch **WAF** on or off in its row. The badge beside the switch shows **Protected** or **Off**. ## Send visitors to HTTPS Switch **Force HTTPS** on in the web proxy's row. Visitors who open an `http://` address are redirected to the same address over `https://`. ## Turn on development mode while you debug 1. Choose how long development mode lasts in **Development mode window**: **1 hour**, **3 hours**, **12 hours**, **24 hours** or **Until switched off**. The list starts at **3 hours**. 2. Switch **Development mode** on in the web proxy's row. The text beside the switch shows the time left, such as `On for 3 more h`, or **On until switched off**. Development mode shows the origin's address and the connection failure on the edge's error pages. The WAF keeps checking requests while it is on. [Use development mode while you debug](/docs/proxies/web-proxies/settings/#use-development-mode-while-you-debug) explains what it shows and when it ends. ## Result Each change shows a confirmation, such as `WAF updated for www.example.com.`, and the row shows the new state. The **Protection** column in [Edge Proxy](https://www.coritan.com/dashboard/proxies/web) shows the same switches as **WAF**, **HTTPS** and **Dev mode** badges. ## Troubleshooting The tab says **No routes yet** : The domain has no web proxy on your account. Select **Connect to a server** to send the domain to one of your servers, or **Add web proxy** to forward it to any origin. A name that goes through the edge is not listed : The tab lists the web proxies on your account under the domain. A name that goes through the edge only because of a proxied `A` or `AAAA` record has no web proxy on your account, and always has the WAF on. Visitors see `The web application firewall blocked your request to protect this website from traffic that looks malicious.` : A request matched enough rules for the WAF to refuse it with `403`. You cannot change the rules or exempt a path. If the WAF refuses real visitors, turn it off for that web proxy and [contact support](/docs/support/) with the address and the time of a refused request. Visitors see `Confirm you are not a bot to continue to example.com.` : The WAF found the request suspicious but not bad enough to refuse, and asks the visitor to prove they are human. A browser continues after the check. Scripts and other clients that are not browsers cannot pass it. ## Related - [How web proxies work](/docs/proxies/web-proxies/) - [Change a web proxy's settings](/docs/proxies/web-proxies/settings/) - [Manage a website's web proxy](/docs/websites/proxy/) ## With the API Each switch is a field of the web proxy: `waf_enabled`, `force_https` and `development_mode`. Change one with `PATCH /api/v1/proxy/routes/{route_id}`, sending only the fields to change: ```bash curl -X PATCH https://api.coritan.com/api/v1/proxy/routes/31 \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"waf_enabled": true, "force_https": true}' ``` The answer is `{"message": "Route updated"}`. To turn on development mode, send `development_mode` as `true` with `development_mode_minutes`, from `0` to `1440`. `0` keeps it on until you turn it off, and leaving the field out gives 180 minutes. A body with no fields answers `400` with `No fields to update`. [Change a web proxy's origin](/docs/proxies/web-proxies/change-the-origin/#with-the-api) lists every field the call takes. # Website settings > Every card on a website's Settings tab, from registration, nameservers and renewal to the DNS zone, DNSSEC and deleting. Source: https://www.coritan.com/docs/websites/settings/ In the dashboard: - /dashboard/websites/…/settings: https://www.coritan.com/dashboard/websites A website's **Settings** tab shows only the cards that apply to the domain. The registrar cards appear for a domain registered with Coritan, **Platform name** for a free name you claimed, and the zone cards when Coritan hosts the domain's DNS. A domain with neither a registration nor a zone here shows **Nothing to configure**. ## Registration The **Registration** card shows the registrar's status as a badge, and these rows: | Row | What it shows | | --- | --- | | **Domain** | The domain name. | | **Registrar status** | The registration's status, such as `active` or `pending_transfer`. | | **Registered** | The registration date. | | **Expires** | The expiry date, and how many days are left. The count is highlighted when fewer than 30 days are left, and shows **Expired** after the date. | | **Auto-renew** | **On** or **Off**. | | **WHOIS privacy** | **On** or **Off**. | | **Transfer lock** | **Locked** or **Unlocked**. | | **DNS zone** | **Hosted here**, which opens the **DNS** tab, or **Elsewhere**. | | **Proxy route** | The web proxy linked to the registration, which opens it in Edge Proxy, or **None**. | | **Certificate** | The certificate linked to the registration, which opens the **SSL** tab, or **None**. | The switches, the nameservers, the EPP code and renewing all need the registration to be `active`. [Statuses](/docs/websites/domains/#statuses) explains each status. ## Domain options | Switch | What it does | Page | | --- | --- | --- | | **Auto-renew** | Renews the domain before it expires. | [Renew a domain](/docs/websites/domains/renew-a-domain/) | | **WHOIS privacy** | Hides your contact details from public WHOIS lookups. | [Turn on WHOIS privacy and transfer lock](/docs/websites/domains/whois-privacy-and-transfer-lock/) | | **Transfer lock** | Makes the registry refuse transfer requests from other registrars. | [Turn on WHOIS privacy and transfer lock](/docs/websites/domains/whois-privacy-and-transfer-lock/) | Each switch saves as soon as you change it. ## Nameservers The **Nameservers** card lists the nameservers the registry sends resolvers to for the domain. **Edit nameservers** opens a list of fields, with **Add nameserver** and a remove button on each field, and **Save nameservers** saves it. The list takes 2 to 13 nameservers. [Change a domain's nameservers](/docs/websites/domains/change-nameservers/) has the steps. ## EPP code The **EPP code** card has **Reveal EPP code**, which shows the code another registrar needs to take the domain over. The button stays off while **Transfer lock** is on. The code shows once, and **Hide** clears it from the screen. [Transfer a domain to another registrar](/docs/websites/domains/transfer-a-domain-away/) has the steps. ## Renewal The **Renewal** card extends the registration by 1 to 10 years: choose the period, then select **Renew domain**. [Renew a domain](/docs/websites/domains/renew-a-domain/) has the steps. ## Platform name For a free name, such as `survival-smp.coritan.gg`, the **Platform name** card shows: | Row | What it shows | | --- | --- | | **Name** | The name you claimed. | | **Delegated from** | The Coritan domain the name sits under, such as `coritan.gg`. | | **Claimed** | The date you claimed it. | | **Renewal** | A free name needs no renewal. It stays yours while its zone exists. | A free name cannot move to a registrar or use other nameservers. [Claim a free coritan.gg name](/docs/proxies/coritan-gg-names/) explains how it works. ## DNS zone The **DNS zone** card appears when Coritan hosts the domain's zone. | Row | What it shows | | --- | --- | | **Status** | `active` while Coritan's nameservers answer for the zone. A `suspended` zone has its queries refused ([Failed payments and suspended services](/docs/billing/failed-payments/)). | | **Serial** | The SOA serial, which goes up with every change to the zone. | | **Records** | How many records the zone holds. | | **SOA contact** | The address the zone's SOA record names as responsible for it, `hostmaster@` the domain until you change it. | To change the contact, select **Edit contact**, enter an address in **SOA contact email** and select **Save contact**. The dashboard asks for a full address, such as `hostmaster@example.com`, and confirms `Zone contact saved.`. Saving raises the zone's serial. [How DNS hosting works](/docs/websites/dns/#the-soa-record) explains the SOA record. ## DNSSEC The **DNSSEC** card shows whether the zone is signed, and for a signed zone: | Row | What it shows | | --- | --- | | **Algorithm** | The signing algorithm. | | **KSK key tag** | The tag of the key-signing key, which the DS records name. | | **ZSK key tag** | The tag of the zone-signing key. | | **Last ZSK rotation** | The date the zone-signing key last changed. | An unsigned zone shows `Signing is off for this zone.` ## DS records for the registrar For a signed zone, the **DS records for the registrar** card lists the DS records to publish at the domain's registrar, each with a copy button. [Set up DNSSEC](/docs/websites/dns/dnssec/) has the steps. ## Delete or release | Card | Button | What it does | Page | | --- | --- | --- | --- | | **Delete the DNS zone** | **Delete zone…** | Deletes the zone and every record in it, after you type the domain. | [Delete a DNS zone](/docs/websites/dns/delete-a-dns-zone/) | | **Release this name** | **Release name…** | Deletes a free name's zone and everything in it, and returns the name to the pool, after you type the name. | [Claim a free coritan.gg name](/docs/proxies/coritan-gg-names/) | ## Change the zone contact with the API `PATCH /api/v1/dns/zones/{zone_id}` changes the zone's `soa_email`, the only field it takes: ```bash curl -X PATCH https://api.coritan.com/api/v1/dns/zones/42 \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"soa_email": "hostmaster@example.com"}' ``` The answer is the zone, with `soa_email` as saved. The API stores the value in lower case. A value without `@`, such as `hostmaster`, stands for that name at the zone itself, `hostmaster@example.com`. `422` with `No fields to update` : The body has no `soa_email`. `422` with `soa_email cannot be empty` or `soa_email too long` : Send an address of 1 to 255 characters. `403` with `Access denied` : The zone is not on your account. ## API - `PATCH /api/v1/dns/zones/{zone_id}`: Update zone (https://www.coritan.com/docs/api/reference/client/dns/#op-patch-api-v1-dns-zones-zone-id) # Troubleshoot websites and DNS > Fixes for domains that do not resolve, records the API refuses, certificates that fail and registrations that stall. Source: https://www.coritan.com/docs/websites/troubleshooting/ Most checks here use `dig`. Add `@ns1.coritan.com` to ask Coritan's nameserver directly, which skips the answers that resolvers keep in their cache. ## The domain does not resolve Check which nameservers answer for the domain: ```bash dig example.com NS +short ``` If the answer does not list Coritan's nameservers, `ns1.coritan.com` to `ns8.coritan.com`, resolvers never ask Coritan. Set the nameservers at the domain's registrar ([Add a domain you already own](/docs/websites/add-an-existing-domain/)), or on the **Settings** tab for a domain registered with Coritan ([Change a domain's nameservers](/docs/websites/domains/change-nameservers/)). If Coritan's nameservers are listed, ask one for the name: ```bash dig @ns1.coritan.com www.example.com A ``` An `NXDOMAIN` status means the zone has no record at that name. Add one on the **DNS** tab ([Add, edit and delete DNS records](/docs/websites/dns/manage-dns-records/)). ## Coritan's nameservers answer REFUSED Coritan's nameservers refuse queries for a domain that has no active zone here. The zone was never added, was deleted, or is suspended with its service. The **DNS zone** card on the domain's **Settings** tab shows a suspended zone's status. [Failed payments and suspended services](/docs/billing/failed-payments/) explains how to get a service running again. ## Resolvers answer SERVFAIL A resolver that checks DNSSEC answers `SERVFAIL` when the DS record at the registrar does not match the zone's key. To confirm, ask with checking turned off: ```bash dig example.com A +cd +short ``` If this gives an address while a plain query gives `SERVFAIL`, DNSSEC is the cause. Replace the DS records at the registrar with the ones on the **DS records for the registrar** card, and delete any DS record left by a previous DNS provider ([Set up DNSSEC](/docs/websites/dns/dnssec/)). A zone you delete and add again gets new keys, so it needs new DS records too. ## A change to a record does not show up Coritan's nameservers answer with a change as soon as you save it. Resolvers that cached the old answer keep it until its TTL runs out. Compare the answer from `ns1.coritan.com` with your resolver's, and lower the TTL before your next planned change ([TTL](/docs/websites/dns/#ttl)). ## The dashboard or the API refuses a record The error names the rule the record breaks, such as a `CNAME` at `@` or a name that a load balancer owns. [Add, edit and delete DNS records](/docs/websites/dns/manage-dns-records/#troubleshooting) lists each error with its fix, and [DNS record types](/docs/websites/dns/record-types/) gives the format for each type. ## An imported zone file has missing or doubled records The import skips lines it cannot read and adds every record beside the ones the zone already has. [Import and export a zone file](/docs/websites/dns/import-and-export-a-zone-file/#troubleshooting) explains what it reads and how to fix a file. ## A load balancer answers with the wrong origin or with nothing Open the load balancer's preview on the **Load balancing** tab to see which pool answers and why. [Create a load balancer](/docs/websites/load-balancing/create-a-load-balancer/#troubleshooting) and the [preview results](/docs/websites/load-balancing/reference/#preview-results) explain each outcome. ## A certificate does not issue The **SSL** tab shows the reason under **The last request failed**. The usual causes are a CAA record that does not allow Coritan's certificate authorities, a name that does not point at Coritan's edge for HTTP validation, and a `TXT` record that is missing or has the wrong value. [Issue a certificate](/docs/websites/ssl/issue-a-certificate/#troubleshooting) lists each error with its fix. ## A certificate did not renew A certificate validated with a manual `TXT` record cannot renew on its own, and any renewal fails when validation no longer works. [Renew or revoke a certificate](/docs/websites/ssl/renew-or-revoke-a-certificate/#troubleshooting) explains what to do before the **Expires** date. ## Visitors see a certificate warning The edge has no valid certificate for the exact name the visitor opened. A certificate for `example.com` does not cover `www.example.com`, so each name needs its own. Check the name's certificate on the **SSL** tab, and [issue one](/docs/websites/ssl/issue-a-certificate/) if it has none. ## Visitors see "This domain is not assigned to any active service on our network" The name resolves to Coritan's edge, but no web proxy serves it. This happens after you delete a web proxy while the name's record stays proxied. [Create a web proxy](/docs/proxies/web-proxies/create-a-web-proxy/) for the name, or turn **Proxied** off for its record on the **DNS** tab so the name resolves to your own address. ## Visitors see a page from the web application firewall The WAF refused the request, or asked the visitor to prove they are human. [Protect a website with the WAF](/docs/websites/waf/#troubleshooting) explains both pages and what you can change. ## A registration stays at pending_transfer Coritan does not move a transfer out of `pending_transfer` by itself. When your old registrar confirms that the domain has left, [contact support](/docs/support/) so the registration can be made active ([Transfer a domain to Coritan](/docs/websites/domains/transfer-a-domain-to-coritan/)). ## You cannot renew a domain or change its settings Renewing, the switches, the nameservers and the EPP code need the registration to be `active`. While it is `expiring_soon`, `expired` or `pending_transfer`, the dashboard cannot change them. [Contact support](/docs/support/) if you need to ([Statuses](/docs/websites/domains/#statuses)). # Edge Proxy > Put a web proxy in front of a site, give a game server a join address, and connect game servers you host elsewhere. Source: https://www.coritan.com/docs/proxies/ In the dashboard: - /dashboard/proxies: https://www.coritan.com/dashboard/proxies - /dashboard/proxies/web: https://www.coritan.com/dashboard/proxies/web - /dashboard/proxies/game: https://www.coritan.com/dashboard/proxies/game A proxy is a hostname that Coritan's edge answers for. The edge takes each visitor's or player's connection for the name and forwards it to your server, which can be hosted here or anywhere else. Proxies are for people who run a website or an app, and for people who run a game server. ## Kinds of proxy *Web proxy* : Puts a hostname you hold, such as `www.example.com`, behind the edge. The edge answers HTTPS for it with a free certificate, checks requests with the web application firewall (WAF) and forwards them to your *origin*: one of your servers here, or any public address. Use one for a website, an app or an API ([How web proxies work](/docs/proxies/web-proxies/)). *Join address* : A name under `coritan.gg`, such as `survival-smp.coritan.gg`, that players type in their game to reach one of your servers hosted here. It carries Minecraft Java, Minecraft Bedrock or another game that connects over TCP ([How join addresses work](/docs/proxies/join-addresses/)). *External server* : A join address for a Minecraft Java or Bedrock server that you run somewhere else, such as at home or with another host. You order it as a plan ([How external servers work](/docs/proxies/external-servers/)). *coritan.gg name* : A free name under `coritan.gg`, such as `alex.coritan.gg`, with a DNS zone on your account. A web proxy can serve it, or a name under it, when you have no domain of your own ([Claim a free coritan.gg name](/docs/proxies/coritan-gg-names/)). *Custom domain* : A name you own, such as `play.example.com`, where players can join a Minecraft server as well as at its join address ([Use your own domain for a join address](/docs/proxies/join-addresses/custom-domain/)). ## The Edge Proxy page In the dashboard, select [Edge Proxy](https://www.coritan.com/dashboard/proxies) in the sidebar. The page lists every proxy on your account. Three cards sit at the top: **Proxies** : How many proxies the account has, split into web proxies and game proxies, such as `Web 2 · Game 3`. **Join domain** : The domain that every join address ends in, such as `coritan.gg`. **Needs attention** : How many proxies have something to look at: a certificate that is not active, an external server that does not answer, a custom domain whose `CNAME` record has gone, or a status such as `suspended` or `pending`. Select the card to list those proxies. The tabs split the list: - **All** lists every proxy, with its **Address**, **Type** (`Web`, `Java`, `Bedrock` or `TCP`), where it **Forwards to**, its **Health** (such as `TLS active` or `Online`), its **Status** and the date it was **Created**. - **Web** lists web proxies, with the **Domain**, the **Origin**, the **Certificate** and the **Protection** badges. The `WAF` and `HTTPS` badges are filled in while each is on, and `Dev mode` appears while development mode is on. - **Game** lists join addresses and external servers, with the **Join address**, the **Edition** and the **Server list** entry. The **Backend** column shows an external server's check (`Online`, `Offline`, `Suspended` or `Not checked`) and a custom domain that is not working yet or has stopped (`CNAME pending` or `CNAME lost`). Type in **Search proxies** to find a proxy by its name, its address or where it forwards. Filter the list with **All**, **Active**, **Needs attention** or **Suspended**. Select a row to open the proxy, and **All proxies** at the top of its page to come back. **New proxy…** creates any kind. It asks what the edge answers for (**Website or app**, **Minecraft Java**, **Minecraft Bedrock** or **TCP service**), then where to forward (**A server hosted here** or **A server elsewhere**). For a game, **A server elsewhere** opens the order page for an external server. When one kind of proxy fails to load, a warning such as `Could not load your web routes` appears above the list, and the rest of the list still shows. Select **Try again** to load it again. ## What you can do Web proxies: - [Create a web proxy](/docs/proxies/web-proxies/create-a-web-proxy/) - [Change a web proxy's origin](/docs/proxies/web-proxies/change-the-origin/) - [Redirect requests with rules](/docs/proxies/web-proxies/redirect-rules/) - [Customise the error page](/docs/proxies/web-proxies/custom-error-page/) - [Change a web proxy's settings](/docs/proxies/web-proxies/settings/): the WAF, HTTPS, caching and development mode Game servers: - [Create a join address](/docs/proxies/join-addresses/create-a-join-address/) for a server hosted here - [Customise the server list entry](/docs/proxies/join-addresses/server-list/) that players see in their game - [Use your own domain for a join address](/docs/proxies/join-addresses/custom-domain/) - [Connect a server you host elsewhere](/docs/proxies/external-servers/connect-an-external-server/), and [manage its plan](/docs/proxies/external-servers/billing/) Names and problems: - [Claim a free coritan.gg name](/docs/proxies/coritan-gg-names/) - [Troubleshoot proxies and join addresses](/docs/proxies/troubleshooting/) A website hosted here has its web proxy on its own page as well ([Manage a website's web proxy](/docs/websites/proxy/)). ## How Edge Proxy is billed Web proxies and join addresses for servers hosted here come with no order and no invoice, and so do coritan.gg names. An external server is a plan that you order and renew like any other service ([Manage an external server's plan](/docs/proxies/external-servers/billing/)). ## Limits and availability - A hostname has one web proxy across all of Coritan, and a name under `coritan.gg` belongs to one join address or claimed name. - A **TCP service** join address forwards only to a server hosted here. An external server carries Minecraft Java or Minecraft Bedrock only. - A **Minecraft Bedrock** join address takes players on Bedrock 26.50 and newer ([Editions and ports](/docs/proxies/join-addresses/#editions-and-ports)). - A server hosted here or an external server has at most one custom domain. - An account holds up to 3 coritan.gg names. - When your account does not offer a kind of proxy, the page leaves out its tab, and **New proxy…** leaves out its choice or marks it `Not available on this account.` # How web proxies work > How a Coritan web proxy serves a hostname, and how its origin, certificate, WAF, redirects and error page fit together. Source: https://www.coritan.com/docs/proxies/web-proxies/ In the dashboard: - /dashboard/proxies/web/…/overview: https://www.coritan.com/dashboard/proxies/web A *web proxy* puts one hostname, such as `www.example.com`, behind Coritan's edge. The edge answers HTTP and HTTPS for the name with a certificate issued for it, checks requests with the web application firewall (WAF), applies your redirects and forwards each remaining request to your *origin*: the server that runs the site. The origin can be one of your servers hosted here or any public address. The dashboard also calls a web proxy a *route*, and gives each one a number such as `Route #31`. ## How the edge handles a request The edge handles each request for the name in this order. The first step that answers ends the request. 1. On plain HTTP, the edge answers certificate validation requests under `/.well-known/acme-challenge/` for certificates it is issuing itself. 2. When the name has no web proxy, the edge answers `404` with a page that reads `This domain is not assigned to any active service on our network.` 3. A [whole-domain redirect](/docs/proxies/web-proxies/redirect-rules/#redirect-the-whole-domain), when set, answers every request. The origin is never contacted. 4. With **Force HTTPS** on, a plain HTTP request gets a `301` redirect to the same address over HTTPS. 5. [Redirect rules](/docs/proxies/web-proxies/redirect-rules/) answer requests whose path matches one, highest priority first. 6. A client that sends too many requests gets `429` with `{"error":"Too many requests"}`. 7. With **WebSockets** on, a request that opens a WebSocket connection goes straight to the origin. 8. The edge answers from its cache when it holds a copy of a static file ([Caching](#caching)). 9. With the **Web application firewall** on, the WAF checks the request ([The web application firewall](#the-web-application-firewall)). 10. A request body larger than the web proxy's limit gets `413` with `{"error":"Request body too large"}`. 11. The edge forwards the request to an origin and passes the origin's answer back to the visitor. ## Origins The origin is an IP address or a hostname, with a port. It must be reachable from the internet: the API refuses an origin on a private or reserved network, such as `10.0.0.10` or `localhost`, and a hostname that does not resolve in public DNS. **Connect to the origin over TLS** makes the edge use HTTPS on the way to the origin. The origin must then present a valid certificate for the origin host from a public certificate authority. A self-signed certificate, an expired one or one for another name fails, and visitors get an error page that names `Origin TLS failed`. When the origin host is an IP address, the certificate must list that address. A web proxy can also have *additional origins*. The edge spreads requests across the origin host and every additional origin, and favours the one with the fewest requests in progress. It does not check the origins' health in advance, so every origin must be able to answer every request. - When the edge cannot connect to an origin, it tries the next one. It tries a `GET`, `HEAD` or `OPTIONS` request twice on each origin. - An origin that keeps failing is skipped for a short time, then tried again. When every origin is skipped, visitors get an error page that names `All origins marked down`. ### Origin protocols The **Origin protocol** sets how the edge talks to the origin. It depends on **Connect to the origin over TLS** as this table shows. | Origin protocol | API value | With TLS to the origin | Without TLS | |---|---|---|---| | **HTTP/1.1** | `h1` | HTTP/1.1 over TLS | HTTP/1.1 | | **HTTP/2 over TLS** | `h2` | HTTP/2 over TLS | HTTP/2 without TLS | | **HTTP/2 cleartext** | `h2c` | HTTP/2 without TLS | HTTP/2 without TLS | | **Negotiate** | `auto` | HTTP/2 or HTTP/1.1, whichever the origin offers | HTTP/1.1 | The origin protocol does not change how visitors connect to the edge. ## What your origin receives - The visitor's method, path and headers, including `Host`, so the origin sees the web proxy's name. - `X-Forwarded-For` and `X-Real-IP` with the visitor's IP address, and `X-Forwarded-Proto` with `https` or `http`. The edge removes any `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto` or `Forwarded` header the visitor sent. - Your custom request headers, on every request. - With **PROXY protocol to origin** on, a PROXY protocol version 2 header at the start of each connection, carrying the visitor's address. The origin must expect it, or every request fails. The path changes when you set a prefix. The edge removes **Strip path prefix** from the start of the path, then adds **Prepend origin path** in front of what is left. With `/api` stripped and `/v2` prepended, a request for `/api/users?page=2` reaches the origin as `/v2/users?page=2`, and a request for `/about` as `/v2/about`. When the origin answers with a `Location` or `Content-Location` header, the edge maps its path back, so visitors get a path they can request. ## Certificates The edge serves HTTPS for the name with a certificate issued for it ([How SSL/TLS certificates work](/docs/websites/ssl/)). A new web proxy requests one while **Request a certificate automatically** is on. Until the certificate is `active`, the edge answers HTTPS for the name with a certificate for another name, and browsers warn visitors. The certificate can only be issued once the name reaches the edge, or when Coritan hosts its DNS zone ([Web proxies and DNS records](#web-proxies-and-dns-records)). [Request or renew the certificate](/docs/proxies/web-proxies/change-the-origin/#request-or-renew-the-certificate) covers the web proxy's **Certificate** card. ## The web application firewall The WAF checks each request against a managed rule set before the request reaches your origin. The rules look for SQL injection, cross-site scripting, command injection, path traversal, server-side request forgery, XML external entities, header injection and vulnerability scanners. They read the path, the query string, the method, the headers and the first 64 KB of the request body. Each rule that matches adds to the request's score. - A request with a high score gets a `403` page that reads `The web application firewall blocked your request to protect this website from traffic that looks malicious.` - A request with a lower score that still looks suspicious gets a page titled `Verify you are human`, which asks the visitor to confirm they are not a bot. A browser that passes is not asked again on that web proxy for 20 minutes, as long as its IP address stays the same. Scripts and API clients cannot pass the check. You cannot see or change the rules, and you cannot exempt a path. The **Web application firewall** switch turns the WAF on or off for the whole web proxy ([Change a web proxy's settings](/docs/proxies/web-proxies/settings/)). The WAF does not check a request that opens a WebSocket connection while **WebSockets** is on, or a file the edge answers from its cache. ## Redirects A web proxy can redirect in two ways ([Redirect requests with rules](/docs/proxies/web-proxies/redirect-rules/)): - *Redirect rules* send matching paths elsewhere, and the other requests still reach the origin. - A *whole-domain redirect* sends every request for the name to another address, and the origin is never contacted. ## Caching The edge keeps copies of static files and answers later requests for them without asking the origin. It caches an answer only when all of these hold: - The request is a `GET` or `HEAD` with no `Authorization` header, and the origin answered `200`. - The answer is an image, a font, an audio file or a video, going by its `Content-Type`. An answer with no type, or with `application/octet-stream`, counts when its path ends in a media extension such as `.png`, `.woff2` or `.mp4`. - The answer has no `Set-Cookie` header, and its `Cache-Control` has neither `no-store` nor `private`, nor a `max-age` of `0`. - The answer states a `Content-Length` of 10 MB or less. - The path does not start with `/api/`, `/ws/`, `/admin`, `/docs`, `/openapi` or `/health`. The edge never caches HTML pages. It caches scripts, styles and JSON only under a path that starts with `/assets/v/` and a version, such as `/assets/v/3f9a1c/app.js`, and only when the origin marks them `immutable`. A cached copy stays for up to an hour, whatever lifetime the origin asks for. Answers carry `X-Cache: HIT` when they come from the cache and `X-Cache: MISS` when they come from the origin. You cannot clear the cache, so give a changed file a new URL when visitors must get it at once. ## Error pages and development mode When the edge cannot reach the origin, it answers with its own error page: `502` when the connection fails and `504` when the origin does not answer within the origin timeout. The page carries a reference called the *ray ID*, which also travels in the `Coritan-Ray` response header, and the `Coritan-Error` header names the failure. - [Customise the error page](/docs/proxies/web-proxies/custom-error-page/) changes the page's words, links and look. - Development mode adds the origin's address and the exact failure while you debug ([Use development mode while you debug](/docs/proxies/web-proxies/settings/#use-development-mode-while-you-debug)). - [Troubleshoot proxies and join addresses](/docs/proxies/troubleshooting/) explains each failure. ## Web proxies and DNS records A web proxy only receives traffic once its name resolves to Coritan's edge. - When Coritan hosts the zone, turn **Proxied** on for the name's `A` or `AAAA` record, and Coritan's nameservers answer with the edge's addresses instead of the record's own ([Proxied records](/docs/websites/dns/#proxied-records)). When you create a web proxy for a name that has no `A`, `AAAA` or `CNAME` record yet, Coritan adds a proxied `A` or `AAAA` record for it, with the origin's address and the comment `Created for the proxy route`. - When another provider hosts the zone, set the name's `A` record at that provider to the edge address. The **SSL** tab shows the address when you choose **HTTP validation** ([Issue a certificate with HTTP validation](/docs/websites/ssl/issue-a-certificate/#issue-a-certificate-with-http-validation)). A proxied record works without a web proxy of your own. When you turn **Proxied** on for a name that has no web proxy, Coritan creates one that forwards to the record's address on port 80 over plain HTTP, with the WAF on, and requests a certificate for it. That web proxy is not on your [Edge Proxy](https://www.coritan.com/dashboard/proxies/web) list and you cannot change it. To choose its port, protocol or error page, create a web proxy of your own for the name instead ([Create a web proxy](/docs/proxies/web-proxies/create-a-web-proxy/)). A proxied record also acts on a web proxy you created for its name: - Changing the record's **Content** changes the web proxy's origin host to the new value. - Turning **Proxied** off, deleting the record or deleting the zone deletes the web proxy, with its settings, redirect rules and error page. Deleting a web proxy leaves its DNS records as they are. A proxied record for the name then sends visitors to the edge, which answers `404` until the name has a web proxy again. ## The Overview tab Open a web proxy from [Edge Proxy](https://www.coritan.com/dashboard/proxies/web) to see its page. The header shows the name, its status, its certificate state, such as `TLS active` or `No certificate`, and where it forwards to. The **Overview** tab has three cards. **Route** : The **Domain**, the **Origins** with their ports and whether each uses TLS, the **Origin protocol**, how the **Path** changes, the names of any **Custom headers**, whether **PROXY protocol** is sent, the **Limits** and when the web proxy was **Created**. **Edit origin** opens the **Origin** tab ([Change a web proxy's origin](/docs/proxies/web-proxies/change-the-origin/)). **Certificate** : The certificate the edge serves for the name: **Issued by**, **Expires**, **Covers** and **Renewed**. It also shows how many certificates the registered domain can still get this week ([Request or renew the certificate](/docs/proxies/web-proxies/change-the-origin/#request-or-renew-the-certificate)). **Switches** : **Web application firewall**, **Force HTTPS**, **WebSockets** and **Development mode**. Each switch saves as soon as you change it ([Change a web proxy's settings](/docs/proxies/web-proxies/settings/)). ## Limits | Limit | Value | |---|---| | Web proxies for one name | One on the whole platform | | Request body | 1–100 MB, 100 MB by default | | Origin timeout | 1–120 seconds, 60 by default | | Request body the WAF reads | The first 64 KB | | Largest file the edge caches | 10 MB | | Time a copy stays in the cache | Up to an hour | | Development mode | Up to 24 hours, or until you turn it off | ## Read web proxies with the API List your web proxies, newest first, with `GET /api/v1/proxy/routes`. It takes `page`, from `1`, and `per_page`, from `1` to `200` with `50` by default: ```bash curl "https://api.coritan.com/api/v1/proxy/routes?page=1&per_page=50" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "routes": [ { "id": 31, "domain": "www.example.com", "upstream_host": "203.0.113.10", "upstream_port": 8080, "upstream_ssl": false, "status": "active", "force_https": true, "websocket_enabled": true, "waf_enabled": true, "ssl_mode": "full", "redirect_to": null, "strip_path_prefix": null, "upstream_path_prefix": null, "upstream_backends": null, "development_mode": false, "development_mode_expires_at": null, "development_mode_active": false, "development_mode_seconds_left": null, "certificate": { "id": 12, "domain": "www.example.com", "status": "active", "issuer": "letsencrypt", "issued_at": "2026-09-01T10:02:11", "expires_at": "2026-11-30T10:02:10", "wildcard": false }, "created_at": "2026-09-01 10:00:04" } ], "total": 1, "page": 1, "per_page": 50 } ``` `certificate` is the certificate the edge serves for the name, or `null`, and its `status` is the certificate's state ([Certificate statuses](/docs/websites/ssl/#certificate-statuses)). Read it rather than `ssl_mode`, which records the last request: `off` when none was made or the certificate was revoked, `pending` after a request, and `full` when the web proxy took over a certificate that already covered the name. `wildcard` is `true` when a wildcard certificate for the parent name covers it. `development_mode_active` says whether the edge still shows diagnostics, because development mode can have expired while `development_mode` is still `true`. Read one web proxy with `GET /api/v1/proxy/routes/{route_id}`: ```bash curl https://api.coritan.com/api/v1/proxy/routes/31 \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` The answer holds every stored setting, including `upstream_protocol`, `send_proxy_protocol`, `grpc_enabled`, `max_body_size_mb`, `timeout_seconds`, `custom_headers`, the `redirect_*` fields and `error_page`. Booleans other than the `development_mode*` fields come back as `0` or `1`, and `custom_headers` and `upstream_backends` come back as JSON text. A web proxy that is not on your account answers `404` with `Route not found`. [Change a web proxy's origin](/docs/proxies/web-proxies/change-the-origin/#with-the-api) describes each field. ## API - `GET /api/v1/proxy/routes`: List proxy routes owned by the current user (https://www.coritan.com/docs/api/reference/client/reverse-proxy/#op-get-api-v1-proxy-routes) - `GET /api/v1/proxy/routes/{route_id}`: Get details of a proxy route you own (https://www.coritan.com/docs/api/reference/client/reverse-proxy/#op-get-api-v1-proxy-routes-route-id) # Create a web proxy > Serve a hostname through Coritan's edge, in front of one of your servers or an address of your own. Source: https://www.coritan.com/docs/proxies/web-proxies/create-a-web-proxy/ In the dashboard: - /dashboard/proxies/web: https://www.coritan.com/dashboard/proxies/web Create a web proxy to serve a hostname, such as `www.example.com`, through Coritan's edge. The edge then answers HTTPS for the name with a free certificate, checks requests with the web application firewall (WAF) and forwards them to your origin ([How web proxies work](/docs/proxies/web-proxies/)). The origin can be one of your servers hosted here, whose address Coritan fills in, or any public address. ## Before you begin - The name is a domain you registered with Coritan, a name in a DNS zone on your account, or a name under your free coritan.gg name. A web proxy for `app.example.com` only needs the `example.com` zone ([Add a domain you already own](/docs/websites/add-an-existing-domain/), [Claim a free coritan.gg name](/docs/proxies/coritan-gg-names/)). - The name has no web proxy yet. Each name can have one web proxy on the whole platform. - To forward to a server hosted here, you need a [Container Apps](/docs/managed-containers/) server with an address and a port. For any other origin, you need its public IP address or hostname and its port. - Visitors reach the web proxy once the name resolves to the edge ([Web proxies and DNS records](/docs/proxies/web-proxies/#web-proxies-and-dns-records)). ## Forward to a server hosted here 1. In the dashboard, go to [Edge Proxy](https://www.coritan.com/dashboard/proxies) and select **New proxy…**. 2. Choose **Website or app**, then **A server hosted here**. The second choice is unavailable while your account has no servers. 3. Enter the **Domain**, such as `www.example.com`. Your free coritan.gg names appear under the field as suggestions, and selecting one fills it in. 4. Choose the **Server**. The form shows the address and port that requests will go to. 5. Leave **Request a certificate automatically** on, so Coritan requests a certificate for the name. 6. Select **Create proxy**. The web proxy forwards to the server's address and port over plain HTTP. It starts with **Force HTTPS**, **WebSockets** and **Web application firewall** on, a 100 MB request body limit and a 60 second origin timeout. If the name already has a web proxy on your account, this points that web proxy at the server and keeps its other settings. The same happens to a web proxy that a proxied DNS record created, which then appears on your list. ## Forward to any address 1. In the dashboard, go to [Edge Proxy](https://www.coritan.com/dashboard/proxies) and select **New proxy…**. 2. Choose **Website or app**, then **A server elsewhere**. 3. Enter the **Domain**, such as `www.example.com`, or select one of the suggestions under the field. 4. Enter the **Origin host**: an IP address, such as `203.0.113.10`, or a hostname, such as `origin.example.net`. Leave out `https://` and the port. 5. Enter the origin's **Port**. It starts at `80`. 6. Choose the **Origin protocol** ([Origin protocols](/docs/proxies/web-proxies/#origin-protocols)). Turn on **Connect to the origin over TLS** when the origin serves HTTPS on that port. 7. Set the switches. All but the last start on: - **Request a certificate automatically** requests a certificate for the name as soon as the web proxy exists. - **Force HTTPS** redirects plain HTTP requests to HTTPS. - **Web application firewall** checks requests before they reach the origin. - **WebSockets** passes WebSocket connections through to the origin. - **PROXY protocol to origin** starts each connection to the origin with a PROXY protocol header that carries the visitor's address. Turn it on only when the origin expects it, or every request fails. 8. To change paths, headers, origins or limits, select **Advanced options** ([Advanced options](#advanced-options)). 9. Select **Create proxy**. ### Advanced options **Strip path prefix** : A path the edge removes from the start of the request path, such as `/api`. **Prepend origin path** : A path the edge adds in front of the request path, such as `/v2`. [What your origin receives](/docs/proxies/web-proxies/#what-your-origin-receives) shows both prefixes at work. **Custom request headers** : Headers the edge adds to every request it forwards. Select **Add header**, then enter a name and a value. **Additional origins** : More origins that share the requests with the origin host. Select **Add origin**, then enter the host and port, choose a protocol or leave `Route default`, and tick **TLS** for an origin that serves HTTPS. The edge does not check their health, so each one must serve the whole site. **Max request body (MB)** : The largest request body the edge accepts, from 1 to 100 MB. It starts at 100. **Origin timeout (s)** : How long the edge waits for the origin to answer, from 1 to 120 seconds. It starts at 60. ## Result The dashboard confirms `Proxy for www.example.com created.` or `www.example.com now points at 203.0.113.10:8080.` and opens the web proxy's **Overview** tab. Coritan requests the certificate in the background, and the **Certificate** card shows its state. When Coritan hosts the zone and the name has no `A`, `AAAA` or `CNAME` record, Coritan also adds a proxied `A` or `AAAA` record for the name. When another provider hosts the zone, point the name at the edge there ([Web proxies and DNS records](/docs/proxies/web-proxies/#web-proxies-and-dns-records)). ## Troubleshooting `Enter a full hostname such as app.example.com.` : The **Domain** needs at least two labels made of letters, digits and hyphens, such as `www.example.com`. `Leave https:// out of the origin host. The port and TLS have their own fields.` : Enter only the host in **Origin host**. Put the port in **Port**, and turn on **Connect to the origin over TLS** for HTTPS. `Leave the port out of the origin host. It has its own field.` : Remove `:8080` or any other port from **Origin host**, and enter it in **Port**. `The origin host is a hostname or an IP address, such as origin.example.net.` : **Origin host** holds a character that no hostname has, such as a space or a slash. Enter the bare hostname or address. `The origin port must be between 1 and 65535.` or `Origin 203.0.113.11 needs a port between 1 and 65535.` : Enter a port from 1 to 65535 for the origin host, or for the additional origin that the message names. `Every custom header needs a name.` : A custom request header has a value but no name. Enter the name, or select **Remove header**. `You must own this domain (via registration or DNS zone) to create a route` : The name is not under a domain you registered here or a DNS zone on your account. Add the zone first ([Add a domain you already own](/docs/websites/add-an-existing-domain/)). The form for a server hosted here says `You must own this domain (via registration or DNS zone)`. `A route for this domain already exists` : The name already has a web proxy. If it is not on your [Edge Proxy](https://www.coritan.com/dashboard/proxies/web) list, a proxied `A` or `AAAA` record made it: forward the name to a server hosted here, which takes that web proxy over, or turn **Proxied** off for the record, create the web proxy and turn **Proxied** on again. If the name has no proxied record, another account holds the web proxy, and you can [contact support](/docs/support/). `Domain route owned by another user` : Another account has a web proxy for the name. [Contact support](/docs/support/). `Server has no allocated IP/port. Ensure the server has an active allocation.` : The server has no address and port yet. Wait until its setup finishes, or choose another server. `Unable to resolve hostname: origin.example.net` : The origin host does not resolve in public DNS. Check its spelling, or enter the origin's IP address. `host targets a private or reserved network`, `resolved address targets a private or reserved network` or `Hostname targets a private or reserved name` : The edge only forwards to public addresses. It refuses private and reserved networks, `localhost`, and names that end in `.local`, `.localhost` or `.internal`. Enter the origin's public address. **A server hosted here** is unavailable : Your account has no servers. [Order a server](https://www.coritan.com/dashboard/order?order=container), or choose **A server elsewhere** and enter the origin's address. ## Related - [How web proxies work](/docs/proxies/web-proxies/) - [Change a web proxy's origin](/docs/proxies/web-proxies/change-the-origin/) - [Redirect requests with rules](/docs/proxies/web-proxies/redirect-rules/) - [Manage a website's web proxy](/docs/websites/proxy/) ## With the API Create a web proxy for any origin with `POST /api/v1/proxy/routes`. `domain` is required, and so is `upstream_host` unless you send `redirect_to` for a [whole-domain redirect](/docs/proxies/web-proxies/redirect-rules/#redirect-the-whole-domain). Send `domain` in lowercase. ```bash curl -X POST https://api.coritan.com/api/v1/proxy/routes \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "domain": "www.example.com", "upstream_host": "203.0.113.10", "upstream_port": 8080, "auto_ssl": true }' ``` The call takes every field that [Change a web proxy's origin](/docs/proxies/web-proxies/change-the-origin/#with-the-api) lists, except `development_mode`, `development_mode_minutes` and `error_page`, and a field you leave out takes the default shown there. It also takes `auto_ssl`, `true` by default, which requests a certificate for the name. The answer is `201`: ```json { "id": 31, "domain": "www.example.com", "status": "active", "ssl_status": "pending", "dns": {"zone": "example.com", "name": "www", "created": true, "type": "A"} } ``` `ssl_status` is `pending` when a certificate was requested and `off` when it was not. `dns` is `null` when Coritan does not host the zone or could not add a record, and `created` is `false` when a record already answers for the name. Forward a name to one of your servers with `POST /api/v1/proxy/routes/connect-domain`. It takes the server's `server_uuid`, which `GET /api/v1/client/servers` lists, the `domain` and `auto_ssl`: ```bash curl -X POST https://api.coritan.com/api/v1/proxy/routes/connect-domain \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"server_uuid": "8c2b1f4e-5d3a-4e7b-9f10-2a6c4d8e1b37", "domain": "www.example.com", "auto_ssl": true}' ``` ```json { "message": "Domain connected to server", "domain": "www.example.com", "upstream": "203.0.113.10:8080", "route_id": 31, "ssl_status": "pending", "dns": {"zone": "example.com", "name": "www", "created": true, "type": "A"} } ``` A server that does not exist answers `404` with `Server not found`, and another account's server answers `403` with `You do not own this server`. The other errors are those in [Troubleshooting](#troubleshooting). A value outside its range, such as a `max_body_size_mb` of `200`, answers `422`. ## API - `POST /api/v1/proxy/routes`: Create a proxy route for a domain you own (https://www.coritan.com/docs/api/reference/client/reverse-proxy/#op-post-api-v1-proxy-routes) - `POST /api/v1/proxy/routes/connect-domain`: Connect a user's domain to their container server (https://www.coritan.com/docs/api/reference/client/reverse-proxy/#op-post-api-v1-proxy-routes-connect-domain) # Change a web proxy's origin > Point a web proxy at a different server or address, change its protocol and port, and manage its certificate. Source: https://www.coritan.com/docs/proxies/web-proxies/change-the-origin/ In the dashboard: - /dashboard/proxies/web/…/origin: https://www.coritan.com/dashboard/proxies/web A web proxy's **Origin** tab sets where the edge sends requests for the name: the origin host and port, the protocol, TLS to the origin, the PROXY protocol, additional origins, path prefixes, custom request headers and the two limits. The **Certificate** card on the **Overview** tab requests and renews the certificate that the edge serves for the name. ## Before you begin - The web proxy is on your [Edge Proxy](https://www.coritan.com/dashboard/proxies/web) list. A web proxy that a proxied DNS record created is not on the list, and its origin follows the record's **Content** ([Web proxies and DNS records](/docs/proxies/web-proxies/#web-proxies-and-dns-records)). - The new origin answers on its port from the internet. The edge refuses private and reserved addresses. ## Change the origin 1. In the dashboard, go to [Edge Proxy](https://www.coritan.com/dashboard/proxies/web) and select the web proxy. 2. Select the **Origin** tab. 3. Change the **Origin host**, the **Port** or the **Origin protocol** ([Origin protocols](/docs/proxies/web-proxies/#origin-protocols)). Turn on **Connect to the origin over TLS** when the origin serves HTTPS on that port. 4. Turn on **PROXY protocol to origin** only when the origin expects a PROXY protocol header. An origin that does not expect it fails every request. 5. Under **Paths, headers and extra origins**, change the path prefixes, the custom request headers, the additional origins and the two limits ([Advanced options](/docs/proxies/web-proxies/create-a-web-proxy/#advanced-options)). To drop an additional origin or a header, select **Remove origin** or **Remove header** on its row. 6. Select **Save origin**. While the form holds changes, the caption under it reads `Unsaved changes. The edge picks them up within a few seconds of saving.` **Reset** puts back the saved values. The **Web application firewall**, **Force HTTPS**, **WebSockets** and **Development mode** switches are on the **Overview** tab ([Change a web proxy's settings](/docs/proxies/web-proxies/settings/)). ## Point the web proxy at one of your servers The **Origin** tab takes an address. To send the name to a server hosted here without looking up the server's address, forward the name to the server again: 1. In the dashboard, go to [Edge Proxy](https://www.coritan.com/dashboard/proxies) and select **New proxy…**. 2. Choose **Website or app**, then **A server hosted here**. 3. Enter the web proxy's name as the **Domain** and choose the **Server**. 4. Turn off **Request a certificate automatically** when the web proxy already has an active certificate. 5. Select **Create proxy**. Coritan replaces the origin host and port with the server's address and port, and keeps every other setting. If the old origin used TLS, turn off **Connect to the origin over TLS** on the **Origin** tab unless the server serves HTTPS on its port. For a domain's own web proxy, such as `example.com`, the website's **Proxy** tab does the same ([Repoint the domain at one of your servers](/docs/websites/proxy/#repoint-the-domain-at-one-of-your-servers)). ## Request or renew the certificate The **Certificate** card on the web proxy's **Overview** tab shows the certificate that the edge serves for the name. - When the web proxy has no certificate, or its last issuance failed, or its certificate has expired, select **Request certificate** ([Request a certificate for a web proxy](/docs/websites/ssl/issue-a-certificate/#request-a-certificate-for-a-web-proxy)). - While the certificate authority validates the name, the card shows **Issuance in progress** and checks again every few seconds. It stops checking after three minutes, so reload the page to see the result. - When the certificate is active, select **Renew now** to renew it before Coritan does ([Renew or revoke a certificate](/docs/websites/ssl/renew-or-revoke-a-certificate/)). - The caption at the foot of the card says how many certificates the registered domain can still get this week ([Issuance limit](/docs/websites/ssl/#issuance-limit)). ## Result - Saving the origin shows `Origin updated.`, and the edge uses the new origin within a few seconds. The **Route** card on the **Overview** tab lists the new **Origins**. - Requesting a certificate shows `Certificate requested for www.example.com.`, and renewing one shows `Renewal of the certificate for www.example.com started.` The **Certificate** card shows the new certificate once the authority issues it. ## Troubleshooting The **Origin** tab checks its fields as the form that creates a web proxy does, with the same messages ([Troubleshooting](/docs/proxies/web-proxies/create-a-web-proxy/#troubleshooting)). Visitors get an error page that names `Origin TLS failed` : **Connect to the origin over TLS** is on, and the origin does not present a valid certificate for the origin host. Give the origin a certificate from a public certificate authority for that name, or turn TLS off and use the origin's plain HTTP port. Every request fails after you turn on **PROXY protocol to origin** : The origin does not expect a PROXY protocol header. Turn the switch off, or set the origin's web server to accept PROXY protocol version 2 on that port. Visitors get an error page that names `Connection refused` or `Connect timed out` : Nothing answers on the new port, or a firewall in front of the origin drops connections from the internet. Check the **Port**, then check the origin's firewall ([Troubleshoot proxies and join addresses](/docs/proxies/troubleshooting/)). **The last issuance failed** : The certificate authority could not validate the name, and the card gives its reason. The name must resolve to the edge, or be in a DNS zone that Coritan hosts ([Issue an SSL/TLS certificate](/docs/websites/ssl/issue-a-certificate/#troubleshooting)). `No fields to update` : The API call held no field that it knows, or only `null` values. Send at least one field with a value. `upstream_protocol must be h1, h2, h2c, or auto (got 'h3')` : Send one of the four values in [Origin protocols](/docs/proxies/web-proxies/#origin-protocols). `backend 203.0.113.11: location must be a 3 or 4 letter airport code such as fra` : An additional origin's `location` holds something other than three or four letters. Send an airport code, such as `fra`, or leave `location` out. `backend 203.0.113.11: region must be a 2 to 5 letter region code such as WEU` : An additional origin's `region` is not a region code. Use a code from [Region codes](/docs/websites/load-balancing/reference/#region-codes), or leave `region` out. ## Related - [How web proxies work](/docs/proxies/web-proxies/) - [Create a web proxy](/docs/proxies/web-proxies/create-a-web-proxy/) - [Change a web proxy's settings](/docs/proxies/web-proxies/settings/) - [How SSL/TLS certificates work](/docs/websites/ssl/) ## With the API Change a web proxy with `PATCH /api/v1/proxy/routes/{route_id}`. Send only the fields you change: a field you leave out, or send as `null`, keeps its value. ```bash curl -X PATCH https://api.coritan.com/api/v1/proxy/routes/31 \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "upstream_host": "origin.example.net", "upstream_port": 443, "upstream_ssl": true, "upstream_protocol": "auto", "upstream_backends": [ {"host": "203.0.113.11", "port": 443, "ssl": true} ] }' ``` ```json {"message": "Route updated"} ``` The edge uses the change within a few seconds. The call takes these fields. The default is the value a new web proxy gets when [Create a web proxy](/docs/proxies/web-proxies/create-a-web-proxy/#with-the-api) leaves the field out. | Field | Values | Default | What it sets | |---|---|---|---| | `upstream_host` | An IP address or a hostname | None | The origin host, with no scheme and no port. | | `upstream_port` | `1`–`65535` | `80` | The origin's port. | | `upstream_ssl` | `true` or `false` | `false` | TLS from the edge to the origin. | | `upstream_protocol` | `h1`, `h2`, `h2c` or `auto` | `h1` | How the edge talks to the origin ([Origin protocols](/docs/proxies/web-proxies/#origin-protocols)). | | `send_proxy_protocol` | `true` or `false` | `false` | A PROXY protocol version 2 header at the start of each connection to the origin. | | `grpc_enabled` | `true` or `false` | `false` | gRPC and gRPC-Web requests go to the origin over HTTP/2. | | `force_https` | `true` or `false` | `true` | A `301` redirect from plain HTTP to HTTPS. | | `websocket_enabled` | `true` or `false` | `true` | WebSocket connections pass through to the origin. | | `waf_enabled` | `true` or `false` | `true` | The web application firewall checks requests. | | `max_body_size_mb` | `1`–`100` | `100` | The largest request body, in MB. | | `timeout_seconds` | `1`–`120` | `60` | How long the edge waits for the origin to answer, in seconds. | | `strip_path_prefix` | A path, such as `/api` | None | The path the edge removes from the start of the request path. | | `upstream_path_prefix` | A path, such as `/v2` | None | The path the edge adds in front of the request path. | | `custom_headers` | An object, or a list of `name` and `value` pairs | None | Headers the edge adds to every request it forwards. | | `upstream_backends` | A list of origins | None | The additional origins. | | `redirect_to` | A URL, such as `https://www.example.com` | None | A whole-domain redirect ([Redirect the whole domain](/docs/proxies/web-proxies/redirect-rules/#redirect-the-whole-domain)). | | `redirect_status_code` | `301`, `302`, `307` or `308` | `301` | The whole-domain redirect's status code. | | `redirect_preserve_path` | `true` or `false` | `true` | Whether the whole-domain redirect keeps the request path. | | `redirect_preserve_query` | `true` or `false` | `true` | Whether the whole-domain redirect keeps the query string. | | `development_mode` | `true` or `false` | `false` | Diagnostics on the edge's error pages ([Use development mode while you debug](/docs/proxies/web-proxies/settings/#use-development-mode-while-you-debug)). | | `development_mode_minutes` | `0`–`1440` | `180` | How long development mode stays on. `0` keeps it on until you turn it off. | | `error_page` | An object | None | The web proxy's own error page ([Customise the error page](/docs/proxies/web-proxies/custom-error-page/#with-the-api)). | Only the change call takes `development_mode`, `development_mode_minutes` and `error_page`. A value outside its range, such as a `timeout_seconds` of `300`, answers `422`. Some fields take a special value or a special shape: - In `strip_path_prefix` and `upstream_path_prefix`, Coritan adds a missing leading slash and removes a trailing one, so `api/` is saved as `/api`. An empty string or `/` removes the prefix. - `custom_headers` takes an object, such as `{"X-Forwarded-Site": "www"}`, or a list such as `[{"name": "X-Forwarded-Site", "value": "www"}]`. The value replaces every header the web proxy had, and `{}` removes them all. - Each entry in `upstream_backends` takes a `host`, a `port` (`80` when left out), `ssl` (`false` when left out) and a `protocol` (the web proxy's `upstream_protocol` when left out). The list replaces every additional origin the web proxy had, and `[]` removes them all. - An empty `redirect_to` removes the whole-domain redirect. - With `grpc_enabled` on and `upstream_protocol` set to `h1`, Coritan saves the protocol as `auto`, because gRPC needs HTTP/2. With it off, the edge forwards a gRPC request as an ordinary request. The dashboard has no switch for it. An additional origin can also say where it runs, so that each edge location tries the nearest origins first. `location` is the airport code of the place, in lowercase, such as `fra`. `region` is its region code, such as `WEU` ([Region codes](/docs/websites/load-balancing/reference/#region-codes)); add it when the place is not one of Coritan's locations. When any additional origin has a `location`, an edge location tries the origins in its own location first, then those in its own region, then the rest, nearest region first. The origin host names no place, so the edge tries it after every origin that does. ```json { "upstream_backends": [ {"host": "203.0.113.11", "port": 443, "ssl": true, "location": "fra"}, {"host": "203.0.113.12", "port": 443, "ssl": true, "location": "iad", "region": "ENAM"} ] } ``` Request a certificate for the web proxy's name with `POST /api/v1/proxy/routes/{route_id}/ssl`. It takes no body: ```bash curl -X POST https://api.coritan.com/api/v1/proxy/routes/31/ssl \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json {"message": "SSL certificate issuance requested", "domain": "www.example.com"} ``` The web proxy's `ssl_mode` becomes `pending`. Follow the order through the `certificate` that [Read web proxies with the API](/docs/proxies/web-proxies/#read-web-proxies-with-the-api) returns, and renew a certificate with the calls in [Renew or revoke a certificate](/docs/websites/ssl/renew-or-revoke-a-certificate/#with-the-api). A web proxy that is not on your account answers `404` with `Route not found`. ## API - `PATCH /api/v1/proxy/routes/{route_id}`: Update a proxy route you own (https://www.coritan.com/docs/api/reference/client/reverse-proxy/#op-patch-api-v1-proxy-routes-route-id) - `POST /api/v1/proxy/routes/{route_id}/ssl`: Request SSL certificate issuance for a proxy route you own (https://www.coritan.com/docs/api/reference/client/reverse-proxy/#op-post-api-v1-proxy-routes-route-id-ssl) # Redirect requests with rules > Send visitors from one path or hostname to another address with redirect rules on a web proxy. Source: https://www.coritan.com/docs/proxies/web-proxies/redirect-rules/ In the dashboard: - /dashboard/proxies/web/…/redirects: https://www.coritan.com/dashboard/proxies/web A web proxy's **Redirects** tab holds two kinds of redirect. *Redirect rules* send the paths they match to another address, and every other request still reaches the origin. A *whole-domain redirect* answers every request for the name, and the edge never contacts the origin. ## Before you begin - The web proxy is on your [Edge Proxy](https://www.coritan.com/dashboard/proxies/web) list ([Create a web proxy](/docs/proxies/web-proxies/create-a-web-proxy/)). - Decide where each redirect goes: a full URL, such as `https://www.example.com/new-page`, or a path on the same name, such as `/new-page`. ## Add a redirect rule 1. In the dashboard, go to [Edge Proxy](https://www.coritan.com/dashboard/proxies/web), select the web proxy and select the **Redirects** tab. 2. On the **Path redirects** card, select **New rule…**. 3. Choose the **Match**: **Exact path**, **Path prefix** or **Regular expression** ([How rules match](#how-rules-match)). 4. Enter the **Source path**, such as `/old-page`. 5. Enter the **Destination**: a full URL, or a path on this name that starts with a slash. 6. Choose the **Status code**. **301 Moved permanently** and **308 Permanent redirect** tell browsers and search engines that the move is permanent. **302 Found** and **307 Temporary redirect** say it is temporary. 7. Set the **Priority**. The edge checks rules with a higher priority first. 8. Leave **Keep the query string** on to add the visitor's query string to the destination. 9. Select **Create rule**. ### How rules match The edge checks the enabled rules from the highest priority down, and the first rule that matches answers. Give rules that can match the same path different priorities, because the edge can check two rules with the same priority in either order. A rule tests the request's path only. The query string takes no part in the match, and the edge reads the path as the visitor sent it, so a space is `%20`. **Exact path** : The path must equal the source path, letter case included. `/old-page` matches neither `/old-page/` nor `/Old-Page`. **Path prefix** : Every path that starts with the source path matches. `/old-blog` matches `/old-blog/post-1` and also `/old-blogroll`, so end the source path with a slash, such as `/old-blog/`, to match one folder only. Every matching path goes to the same destination, and the edge does not add the rest of the path to it. **Regular expression** : The edge tests the pattern against the start of the path, with Python's regular expression syntax. End the pattern with `$` to match the whole path. The destination can use the pattern's groups as `\1` or `\g<1>`: with `^/blog/(\d+)/?$` and the destination `/posts/\1`, a request for `/blog/42` goes to `/posts/42`. A destination that is a path stays on the same name over HTTPS, so `/new-page` sends a visitor on `www.example.com` to `https://www.example.com/new-page`. With **Keep the query string** on, the edge adds the visitor's query string after a `?`, or after a `&` when the destination has a query string already. Rules answer before the WAF checks a request and before the edge contacts the origin. With **Force HTTPS** on, a plain HTTP request goes to HTTPS first, and the rule then answers the HTTPS request. While the web proxy has a whole-domain redirect, the edge does not check its rules. ## Change or delete a rule The **Path redirects** table lists the rules from the highest priority down, with the **Source** and its match, the **Destination**, the **Code**, the **Priority** and whether the rule is **Enabled**. - To turn a rule off or on, use its switch in the **Enabled** column. A disabled rule stays in the list but never matches. - To change a rule, open its menu, select **Edit rule…**, make the changes and select **Save rule**. - To delete a rule, open its menu, select **Delete rule…**, then select **Delete rule**. Requests for its path reach the origin again. > [!CAUTION] > You cannot undo deleting a rule. To stop a rule for a while, turn it off instead. ## Redirect the whole domain A whole-domain redirect sends every request for the name to another address, such as `example.com` to `https://www.example.com`. The name that receives the traffic needs its own web proxy or site. 1. On the **Redirects** tab, find the **Whole-domain redirect** card and select **Set redirect**. 2. Enter the **Destination**, a full URL such as `https://www.example.com`. 3. Choose the **Status code**. 4. Leave **Keep the request path** ticked to send each path to the same path at the destination. Untick it to send every request to the destination's home page. 5. Leave **Keep the query string** ticked to keep the visitor's query string. 6. Select **Save redirect**. The redirect answers before **Force HTTPS** and before any redirect rule. The card then describes the redirect. **Edit** changes it, and **Remove** stops it, so requests reach the origin again. ## Result - The dashboard confirms `Redirect rule created.`, `Redirect rule updated.` or `Redirect rule deleted.` for a rule, and `Whole-domain redirect saved.` or `Whole-domain redirect removed.` for the whole-domain redirect. - A matching request gets the status code you chose, with the destination in its `Location` header. ## Troubleshooting `Enter a source path.` or `Enter a destination.` : The field is empty. Fill it in. `Source paths start with a slash, such as /old-page.` : Exact and prefix rules take a path, such as `/old-page`, and never a full URL. To redirect a whole name, use [Redirect the whole domain](#redirect-the-whole-domain). `The regular expression does not compile.` : The pattern has a syntax error, such as a bracket that is never closed. Correct it. `The destination is a full URL (https://…) or a path on this domain (/…).` : Start the destination with `https://`, `http://` or `/`. `The destination is a full URL, such as https://www.example.com.` : A whole-domain redirect needs a full URL that starts with `https://` or `http://`. A rule never matches : Check that the rule is enabled, and that the path matches letter for letter. A rule with a higher priority can answer first, and no rule answers while a whole-domain redirect is set. A regular expression that uses syntax Python does not read, such as the named group `(?…)`, never matches: write `(?P…)` instead. Visitors go round in a loop : The destination matches the rule's own source, such as a prefix rule for `/docs` that sends visitors to `/docs/new`. Use an exact rule, end a regular expression with `$`, or choose a destination outside the source path. Visitors still get a redirect you changed or deleted : Browsers keep `301` and `308` redirects and follow them without asking again. Clear the browser's cache to test, and use `302` or `307` while you try a rule out. ## Related - [How web proxies work](/docs/proxies/web-proxies/) - [Change a web proxy's origin](/docs/proxies/web-proxies/change-the-origin/) - [Change a web proxy's settings](/docs/proxies/web-proxies/settings/) ## With the API List a web proxy's rules with `GET /api/v1/proxy/routes/{route_id}/redirect-rules`. The answer is a list, from the highest priority down: ```bash curl https://api.coritan.com/api/v1/proxy/routes/31/redirect-rules \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json [ { "id": 7, "source_pattern": "/old-page", "match_type": "exact", "target_url": "/new-page", "status_code": 301, "preserve_query": true, "priority": 10, "enabled": true, "created_at": "2026-09-02 08:15:40" } ] ``` Add a rule with `POST /api/v1/proxy/routes/{route_id}/redirect-rules`. `source_pattern` and `target_url` are required. `match_type` is `exact`, `prefix` or `regex`, `exact` by default. `status_code` is `301`, `302`, `307` or `308`, `301` by default. `preserve_query` and `enabled` are `true` by default, and `priority` is `0`. ```bash curl -X POST https://api.coritan.com/api/v1/proxy/routes/31/redirect-rules \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"source_pattern": "^/blog/(\\d+)/?$", "match_type": "regex", "target_url": "/posts/\\1", "status_code": 308, "priority": 20}' ``` ```json {"id": 8, "message": "Redirect rule created"} ``` The API does not check the source path or the destination the way the dashboard does. Start an exact or prefix source path with a slash, make sure the edge can read a regular expression, and send a destination that is a full URL or a path. Change a rule with `PATCH /api/v1/proxy/routes/{route_id}/redirect-rules/{rule_id}`, with only the fields you change. It answers `{"message": "Redirect rule updated"}`, and `No fields to update` when the body holds none. Delete a rule with `DELETE` on the same path, which answers `{"message": "Redirect rule deleted"}`: ```bash curl -X DELETE https://api.coritan.com/api/v1/proxy/routes/31/redirect-rules/8 \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` A `match_type` or `status_code` outside the lists answers `400` with `match_type must be exact, prefix, or regex` or `status_code must be 301, 302, 307, or 308`. A web proxy that is not on your account answers `404` with `Route not found`. The whole-domain redirect is part of the web proxy itself. Set it with `PATCH /api/v1/proxy/routes/{route_id}` and the `redirect_to`, `redirect_status_code`, `redirect_preserve_path` and `redirect_preserve_query` fields, and remove it with an empty `redirect_to` ([Change a web proxy's origin](/docs/proxies/web-proxies/change-the-origin/#with-the-api)): ```bash curl -X PATCH https://api.coritan.com/api/v1/proxy/routes/31 \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"redirect_to": "https://www.example.com", "redirect_status_code": 301, "redirect_preserve_path": true}' ``` A web proxy that only redirects needs no origin. Create it with `redirect_to` in place of `upstream_host` ([Create a web proxy](/docs/proxies/web-proxies/create-a-web-proxy/#with-the-api)). ## API - `GET /api/v1/proxy/routes/{route_id}/redirect-rules`: List redirect rules for a route you own (https://www.coritan.com/docs/api/reference/client/reverse-proxy/#op-get-api-v1-proxy-routes-route-id-redirect-rules) - `POST /api/v1/proxy/routes/{route_id}/redirect-rules`: Create a redirect rule for a route you own (https://www.coritan.com/docs/api/reference/client/reverse-proxy/#op-post-api-v1-proxy-routes-route-id-redirect-rules) - `PATCH /api/v1/proxy/routes/{route_id}/redirect-rules/{rule_id}`: Update a redirect rule for a route you own (https://www.coritan.com/docs/api/reference/client/reverse-proxy/#op-patch-api-v1-proxy-routes-route-id-redirect-rules-rule-id) - `DELETE /api/v1/proxy/routes/{route_id}/redirect-rules/{rule_id}`: Delete a redirect rule for a route you own (https://www.coritan.com/docs/api/reference/client/reverse-proxy/#op-delete-api-v1-proxy-routes-route-id-redirect-rules-rule-id) # Customise the error page > Change the page visitors see when a web proxy cannot reach its origin, with your own wording, colours and links. Source: https://www.coritan.com/docs/proxies/web-proxies/custom-error-page/ In the dashboard: - /dashboard/proxies/web/…/error-page: https://www.coritan.com/dashboard/proxies/web When the edge cannot reach a web proxy's origin, it answers with its own error page: `502` when the connection fails, and `504` when the origin does not answer within the origin timeout. A web proxy's **Error page** tab puts your own heading, message, logo, links and colours on that page. Until you turn your own page on, visitors get the Coritan page, or the page of the brand that the web proxy belongs to when that brand has one turned on. The note at the top of the **When the origin is down** card says which. ## Before you begin - The web proxy is on your [Edge Proxy](https://www.coritan.com/dashboard/proxies/web) list. - A logo needs an `https://` address. The edge copies the image into the page, so it must be a PNG, JPEG, GIF, WebP or SVG file of 96 KB or less. - Links need an `https://` address. The support link can also be a `mailto:` address. ## Set up your error page 1. In the dashboard, go to [Edge Proxy](https://www.coritan.com/dashboard/proxies/web), select the web proxy and select the **Error page** tab. 2. Turn on the switch at the top of the **When the origin is down** card. The card then says `Your page`. 3. Enter a **Heading**, up to 80 characters, and a **Message**, up to 500. The message keeps its line breaks. An empty field shows Coritan's words for the failure. 4. Under **How much of the failure the page explains**, choose one: - **Everything** shows a diagram of the path from Coritan to your domain to the origin, and a panel with the ray ID, the edge, the time and the failure. - **A reference** shows one line with the ray ID and the time, which a visitor can quote to you. - **Words only** shows your heading, your message and your links. The ray ID still travels in the `Coritan-Ray` response header. 5. To show your logo, enter its **Logo URL**. 6. To link to a place where visitors can follow the outage, such as a Discord server or a status page, enter the **Updates link** and its **Button label**. The label is `Updates` unless you change it. 7. To link to your support, enter the **Support link**, such as `mailto:alex@example.com`, and its **Button label**. The label is `Contact support` unless you change it. 8. Choose the page's look ([Choose the look](#choose-the-look)). 9. Check the preview beside the form, then select **Save error page**. While the form holds changes, the caption under it reads `Unsaved changes. The edge picks them up within a few seconds of saving.` **Reset** puts back the saved page. ### Choose the look **Palette** : **Light** gives a pale page, a white card and dark text. **Dark** gives a near-black page and card with light text. **Page background**, **Card**, **Text** and **Accent** : Colours as `#rrggbb` values, such as `#102a43`, over the palette's own. The accent colours the heading and the support button. An empty field keeps the palette's colour. **Corners** : **Sharp corners**, **Soft corners** or **Rounded**. **Font** : **Inter (loaded from Google Fonts)** or **The visitor's system font**. The page cannot use a font from your own site, because your site is what is down. **How the page says 502** : **Small line above the heading**, **Rounded badge with a status dot**, **Sharp status tag under the message** or **Quietly, in the reference line under the message**. The status code always appears somewhere on the page. Every page keeps the `Error 502` or `Error 504` label, the `Powered by Coritan` footer and the `Coritan-Ray` header, so visitors and support staff can tell which network answered. While development mode is on, the page shows the full diagnostics whatever you choose here ([Use development mode while you debug](/docs/proxies/web-proxies/settings/#use-development-mode-while-you-debug)). ## Preview the page The preview shows the page as a visitor would see it, and it follows your changes as you type. Choose **502 · origin down** or **504 · origin too slow** above it to see each page. With the switch off, it shows the page visitors get today. The preview runs no scripts and uses the dashboard's fonts, so text in Inter looks slightly different on the live page. When it shows **A field is not valid yet**, correct the field that the form marks. **Could not render the preview** means only the preview failed, and saving still works. ## Turn your page off Turn the switch off and select **Save error page**. Visitors get the Coritan page or the brand's page again, and your words and settings stay saved for the next time you turn the switch on. ## Result The dashboard confirms `Error page saved. The edge picks it up within a few seconds.`, or `Error page saved.` when the switch is off. The next `502` or `504` for the name shows the page. ## Troubleshooting `The heading is limited to 80 characters.` or `The message is limited to 500 characters.` : Shorten the text. The counter under each field shows how many characters it holds. `The link label is limited to 40 characters.` or `The updates label is limited to 40 characters.` : Shorten the **Button label** of the support link or of the updates link. `The logo must be an https:// URL.` or `The updates link must be an https:// URL.` : Enter an address that starts with `https://`. `The support link must be an https:// URL or a mailto: address.` : Enter an `https://` address, or an email address written as `mailto:alex@example.com`. `The accent colour must be a #rrggbb value.` : Enter the colour as `#` and six hexadecimal digits, such as `#1d4ed8`. The same message names the background, card or text colour when that field is wrong. The logo does not appear : The edge could not copy the image when it published the page: the file is larger than 96 KB, is not a PNG, JPEG, GIF, WebP or SVG image, or did not load within five seconds. The page then links to the image instead, and a logo on the origin that is down cannot load. Host a smaller image somewhere other than the origin. The edge keeps a copied image for up to six hours, so give a new logo a new URL. Visitors get a different page : The error page covers `502` and `504` only. A `404` for a name with no web proxy, a `403` from the WAF and a `429` for too many requests keep their own pages ([How web proxies work](/docs/proxies/web-proxies/#how-the-edge-handles-a-request)). ## Related - [How web proxies work](/docs/proxies/web-proxies/) - [Change a web proxy's settings](/docs/proxies/web-proxies/settings/) - [Troubleshoot proxies and join addresses](/docs/proxies/troubleshooting/) ## With the API The error page is the `error_page` field of `PATCH /api/v1/proxy/routes/{route_id}` ([Change a web proxy's origin](/docs/proxies/web-proxies/change-the-origin/#with-the-api)). Send the whole page each time, because the value replaces the saved page. `{}` removes it. ```bash curl -X PATCH https://api.coritan.com/api/v1/proxy/routes/31 \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "error_page": { "enabled": true, "heading": "We will be right back", "message": "Our site is down for a few minutes while we fix a problem.", "detail": "reference", "support_url": "mailto:alex@example.com", "theme": "dark", "accent_color": "#22c55e" } }' ``` | Field | Values | When left out | |---|---|---| | `enabled` | `true` or `false` | `false`, so visitors get the inherited page | | `heading` | Text, up to 80 characters | Coritan's heading for the failure | | `message` | Text, up to 500 characters | Coritan's sentence for the failure | | `detail` | `full`, `reference` or `minimal` | `full` | | `logo_url` | An `https://` URL | No logo | | `logo_data` | A base64 `data:` URI of a PNG, JPEG, GIF, WebP or SVG image | Copied from `logo_url` | | `support_url` | An `https://` URL or a `mailto:` address | No support button | | `support_label` | Text, up to 40 characters | `Contact support` | | `updates_url` | An `https://` URL | No updates button | | `updates_label` | Text, up to 40 characters | `Updates` | | `theme` | `light` or `dark` | `light` | | `background_color`, `surface_color`, `text_color`, `accent_color` | `#rrggbb` | The palette's colour | | `radius` | `sharp`, `soft` or `round` | `soft` | | `font` | `inter` or `system` | `inter` | | `status_style` | `plain`, `pill`, `tag` or `inline` | `plain` | `full`, `reference` and `minimal` are **Everything**, **A reference** and **Words only**. `surface_color` is the card's colour. URLs can be up to 2,048 characters long. Any other value answers `400` with the reason after `error_page: `, such as `error_page: accent_color must be a #rrggbb colour` or `error_page: theme must be one of light, dark`. `GET /api/v1/proxy/routes/{route_id}` returns the saved page as `error_page`, or `null` when there is none. Render a page without saving it with `POST /api/v1/proxy/routes/{route_id}/error-page/preview`. It takes the `error_page` to render, or `null` for the page visitors get today, and a `status` of `502` or `504`, `502` by default: ```bash curl -X POST https://api.coritan.com/api/v1/proxy/routes/31/error-page/preview \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"error_page": {"enabled": true, "heading": "We will be right back"}, "status": 504}' \ -o preview.html ``` The answer is the page's HTML, with a made-up ray ID and failure. A page that does not pass the checks answers `400` with the same messages as the change call. ## API - `POST /api/v1/proxy/routes/{route_id}/error-page/preview`: Render the page visitors would see, for a block that is not saved yet (https://www.coritan.com/docs/api/reference/client/reverse-proxy/#op-post-api-v1-proxy-routes-route-id-error-page-preview) # Change a web proxy's settings > Turn HTTPS redirects, the WAF, WebSockets and development mode on or off for a web proxy, or delete it. Source: https://www.coritan.com/docs/proxies/web-proxies/settings/ In the dashboard: - /dashboard/proxies/web/…/settings: https://www.coritan.com/dashboard/proxies/web The **Switches** card on a web proxy's **Overview** tab turns the web application firewall, **Force HTTPS**, **WebSockets** and development mode on or off. The **Settings** tab shows the web proxy's name and number, checks how the name resolves when Coritan hosts its DNS zone, and deletes the web proxy. ## Before you begin - The web proxy is on your [Edge Proxy](https://www.coritan.com/dashboard/proxies/web) list. A web proxy that a proxied DNS record created is not, and you change it through the record ([Web proxies and DNS records](/docs/proxies/web-proxies/#web-proxies-and-dns-records)). ## Turn a switch on or off 1. In the dashboard, go to [Edge Proxy](https://www.coritan.com/dashboard/proxies/web) and select the web proxy. Its **Overview** tab opens. 2. On the **Switches** card, turn the switch on or off. It saves at once. **Web application firewall** : Checks each request against the managed rule set before it reaches the origin ([The web application firewall](/docs/proxies/web-proxies/#the-web-application-firewall)). With it off, every request that passes the other steps reaches the origin unchecked. **Force HTTPS** : Redirects plain HTTP requests to the same address over HTTPS with a `301`. Certificate validation over HTTP still works while it is on. **WebSockets** : Passes WebSocket connections through to the origin. With it off, a visitor's WebSocket connection fails. **Development mode** : Adds diagnostics to the edge's error pages for a time you choose ([Use development mode while you debug](#use-development-mode-while-you-debug)). ## Use development mode while you debug When the edge cannot reach the origin, its error page shows a ray ID, the edge, the time and the name of the failure, such as `Connection refused`. Development mode adds what you need to find the cause: - The web proxy's number, the failure's reason code and how many origins the edge tried. - A table of each origin the edge tried, with its address, the protocol, the result and how long the attempt took. - The full diagnostics, even when your error page is set to show less ([Customise the error page](/docs/proxies/web-proxies/custom-error-page/)). - On every answer that succeeds, a `Coritan-Origin` header with the address of the origin that answered, next to `Coritan-Ray` and `Coritan-Edge`. > [!WARNING] > While development mode is on, anyone who gets an error page or reads the response headers can see your origin's address. Turn it off when you have found the cause. 1. On the **Switches** card, choose how long it stays on under **Turn off after**: **1 hour**, **3 hours**, **12 hours**, **24 hours** or **Until switched off**. It starts at **3 hours**. 2. Turn on **Development mode**. The switch then shows the time left and when it ends, such as `On for 2 more h`, or `On until switched off`. When the time runs out, the edge stops showing the diagnostics and the switch shows `Expired`. To turn development mode on again, or to change its time, turn it off, choose a new time and turn it on again. ## Check how the name resolves When Coritan hosts the DNS zone for the name, the **Settings** tab has a **DNS** card. It lists the name's `A`, `AAAA` and `CNAME` records and says where visitors go: **Traffic goes through the edge** : Every `A` and `AAAA` record for the name is proxied, so resolvers get the edge's addresses. **Visitors reach the origin directly** : At least one address record is not proxied. Visitors who get its address skip the edge, the certificate and the WAF. Turn **Proxied** on for the record ([Edit a record](/docs/websites/dns/manage-dns-records/#edit-a-record)). **Nothing points here yet** : The zone has no `A`, `AAAA` or `CNAME` record for the name. Add a proxied `A` record with the origin's address ([Add, edit and delete DNS records](/docs/websites/dns/manage-dns-records/)). A `CNAME` record shows `Follows the target`: visitors go wherever its target resolves. **Open zone** opens the zone's **DNS** tab. When another provider hosts the zone, the card does not appear, and you check the name's records there. ## Delete a web proxy > [!WARNING] > Deleting a web proxy deletes its settings, its redirect rules and its error page, and you cannot undo it. Visitors who reach the edge for the name get an error page until the name has a web proxy again. 1. On the web proxy's **Settings** tab, find the **Delete this route** card and select **Delete route…**. 2. Type the web proxy's name, such as `www.example.com`, to confirm. 3. Select **Delete route**. Deleting leaves the name's DNS records as they are. A proxied record keeps sending visitors to the edge, which answers `404` for the name. Turn **Proxied** off for the record to send visitors straight to your server. The certificate stays on your account, and a new web proxy for the name uses it. You cannot change a web proxy's name, because its certificate, WAF and DNS answer belong to the name. To serve a different name, create a web proxy for it ([Create a web proxy](/docs/proxies/web-proxies/create-a-web-proxy/)), then delete this one. ## Result - Each switch confirms its change, such as `WAF updated.`, `Force HTTPS updated.`, `WebSockets updated.` or `Development mode updated.` The edge applies it within a few seconds. - Deleting shows `Route for www.example.com deleted.` and returns to the list of web proxies. ## Troubleshooting The WAF blocks requests your site needs : You cannot change the WAF's rules or exempt a path. Turn the **Web application firewall** off for this web proxy, or serve the traffic that the WAF blocks, such as an API, from a name with a web proxy of its own and the WAF off. The switch shows `Expired` : Development mode ran out, and the edge no longer shows the diagnostics. Turn the switch off, choose a new time under **Turn off after** and turn it on again. The name answers `404` after you delete its web proxy : The name's record is proxied, so visitors still reach the edge, and the name has no web proxy. Create a web proxy for the name, or turn **Proxied** off for the record and on again, which gives the name a web proxy to the record's address. ## Related - [How web proxies work](/docs/proxies/web-proxies/) - [Change a web proxy's origin](/docs/proxies/web-proxies/change-the-origin/) - [Customise the error page](/docs/proxies/web-proxies/custom-error-page/) - [Troubleshoot proxies and join addresses](/docs/proxies/troubleshooting/) ## With the API The switches are fields of `PATCH /api/v1/proxy/routes/{route_id}`: `waf_enabled`, `force_https`, `websocket_enabled` and `development_mode` ([Change a web proxy's origin](/docs/proxies/web-proxies/change-the-origin/#with-the-api)). This call turns development mode on for an hour: ```bash curl -X PATCH https://api.coritan.com/api/v1/proxy/routes/31 \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"development_mode": true, "development_mode_minutes": 60}' ``` ```json {"message": "Route updated"} ``` `development_mode_minutes` runs from `0` to `1440`. `0` keeps development mode on until you turn it off, and leaving the field out gives 180 minutes. Send `{"development_mode": false}` to turn it off. `GET /api/v1/proxy/routes/{route_id}` returns the state the edge reads: `development_mode_active`, `development_mode_expires_at` in UTC, and `development_mode_seconds_left`, which is `null` when development mode has no end. Delete a web proxy with `DELETE /api/v1/proxy/routes/{route_id}`: ```bash curl -X DELETE https://api.coritan.com/api/v1/proxy/routes/31 \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json {"message": "Route deleted"} ``` The call deletes the web proxy with its redirect rules at once, and asks for no confirmation. A web proxy that is not on your account answers `404` with `Route not found`. ## API - `DELETE /api/v1/proxy/routes/{route_id}`: Delete a proxy route you own (https://www.coritan.com/docs/api/reference/client/reverse-proxy/#op-delete-api-v1-proxy-routes-route-id) # How join addresses work > How a join address carries players to a game server through Coritan's edge, for Minecraft Java, Bedrock and other TCP games. Source: https://www.coritan.com/docs/proxies/join-addresses/ In the dashboard: - /dashboard/proxies/game/…/overview: https://www.coritan.com/dashboard/proxies/game A *join address* is a hostname, such as `survival-smp.coritan.gg`, that players type in their game to reach your server. Players connect to Coritan's edge, which reads the name they asked for and forwards the connection to the server behind it. Players never see your server's own IP address and port. The dashboard also calls a join address a *game proxy*, and gives each one a number such as `Route #42`. Join addresses on coritan.com end in `coritan.gg`, the *join domain*. The **Join domain** card on the [Edge Proxy](https://www.coritan.com/dashboard/proxies/game) page shows it. A join address forwards to one of your servers hosted here, or to a server you run elsewhere as an [external server](/docs/proxies/external-servers/). ## Editions and ports Each join address carries one kind of game traffic, called its *edition*. You choose it when you [create the join address](/docs/proxies/join-addresses/create-a-join-address/). | Edition | Players join at | Edge port | Who can join | |---|---|---|---| | **Minecraft Java** | `survival-smp.coritan.gg` | `25565` | Players on Minecraft Java Edition | | **Minecraft Bedrock** | `survival-smp.coritan.gg`, port `19132` | `19132` | Players on Minecraft Bedrock 26.50 and newer | | **TCP service** | `survival-smp.coritan.gg:61234` | A port of its own, which the edge picks | Players of any game that connects over TCP | `25565` and `19132` are the games' default ports, so players type only the name. Every Java join address shares port `25565`, and every Bedrock join address shares port `19132`: the edge tells them apart by the name the player typed. A plain TCP connection carries no name, so each **TCP service** join address gets a port of its own on the edge, and players type that port after the name. Two editions have limits of their own: - A **Minecraft Bedrock** join address carries NetherNet, the connection that Minecraft Bedrock uses from version 26.50. Older Bedrock clients cannot join through it. The server behind it must accept NetherNet connections too, and a Bedrock server that speaks only the older RakNet protocol cannot take players through a join address. - A **TCP service** join address forwards only to a server hosted here, and its edge port is in the region where the server runs. ## How a player reaches your server 1. The player's game looks up the join address. It resolves to Coritan's edge in the region where your server runs. 2. The edge reads the name the player asked for and finds its join address. 3. The edge opens a connection to your server and passes the player's traffic both ways. If your server does not accept the connection, the edge ends the join with the server's offline message ([What players see](#what-players-see)). ## What your server receives Your server sees each player's connection arrive from the edge's address. The edge can pass on the player's own address in one of two ways: - With **PROXY protocol** on, the edge starts each connection with a PROXY protocol version 2 header that carries the player's address. The server must expect the header, or every join fails. Bedrock join addresses cannot use it. - With PROXY protocol off, a Java join address adds the player's address to the server address in the Minecraft handshake, in the form `survival-smp.coritan.gg///203.0.113.50:53422///1790000000`. A plugin that reads this form can recover the player's address, and a server without one ignores the extra text. Without either, your server's logs and IP bans see the edge's address for every player. ## Join addresses and your server A join address that forwards to a server hosted here follows the server. When the server moves, or its address or port changes, Coritan points the join address at the new address. A server can have more than one join address. - When the server is suspended, its join addresses are suspended too, and they come back when the server does. - When you delete the server, Coritan deletes its join addresses and any custom domain linked to them. The names become free for anyone to take. - You cannot rename a join address, or delete it while its server stays. To offer players another name, create another join address for the same server. An external server's join address follows the external server's plan instead ([Manage an external server's plan](/docs/proxies/external-servers/billing/)). ### Join addresses Coritan creates for you When the join address policy's `auto_provision_minecraft` is `true` ([Read join addresses with the API](#read-join-addresses-with-the-api)), Coritan gives each new Minecraft server a join address while it sets the server up. The address is named after the server when that name is free and follows the [name rules](/docs/proxies/join-addresses/create-a-join-address/#name-rules). Otherwise the server gets none, and you [create one yourself](/docs/proxies/join-addresses/create-a-join-address/). ## What players see What a player's server list shows, and what happens when they join, depends on the state of the join address and your server. | Situation | The server list shows | A player who joins | |---|---|---| | Your server answers | Your server's own entry, with any changes from the **Server list** tab | Reaches your server | | Your server does not answer | The offline message, with no players | Sees the offline message | | A free server is asleep | `Asleep. Join to start the server` | Starts the server ([How free servers work](/docs/managed-containers/free-servers/#sleeping-and-waking)) | | The join address is suspended | Nothing, because the name stops resolving | Gets the game's own error for a server it cannot find | | No join address has the name | Nothing, because the name does not resolve | Gets the game's own error for a server it cannot find | The offline message is the one on the join address's **Server list** tab, or `Server is currently offline` when that is empty ([Customise the server list entry](/docs/proxies/join-addresses/server-list/)). A name that reaches the edge without a join address of its own, such as a domain whose record points at the edge before you link it, gets `There is no proxy associated with this domain.` in the server list and on joining. The edge also protects your server from floods of connections: - While an edge is under a flood, or when one address has too many connections open at once, the edge asks the player to join again with `Please rejoin to verify your connection.` The second attempt goes through. - An address that opens too many connections in a short time is blocked for a while, and its players see `Your IP is temporarily blocked for suspicious activity.` - When an edge carries all the players it can take, new joins get `This proxy is at capacity. Please try again shortly.` ## The Overview tab Open a join address from [Edge Proxy](https://www.coritan.com/dashboard/proxies/game) to see its page. The header shows its name, its status and edition, the join address with a copy button, the server it forwards to and its number. The **Overview** tab has three cards. **Route** : The **Join address**, the **Edition**, the server it **Forwards to** with a link to the server's page, the **Port** players join on (**Edge port** for a TCP service), whether **PROXY protocol** is on, the **Status**, the **Status cache** and when it was **Created**. For a Bedrock join address, the **Port** row shows the Java port; Bedrock players join on `19132`. **Server list** : The **Name**, **Online MOTD**, **Offline MOTD**, **Version string** and **Icon** that players see in their server list. **Edit** opens the **Server list** tab ([Customise the server list entry](/docs/proxies/join-addresses/server-list/)). **Custom domain** : The domain you linked to the join address and whether it is verified. **Link one** or **Manage** opens the **Custom domain** tab ([Use your own domain for a join address](/docs/proxies/join-addresses/custom-domain/)). The **Status** is `active` while players can join, and `suspended` while the server is suspended. An external server's page has more tabs and a **Your server** card ([How external servers work](/docs/proxies/external-servers/)). ## Limits | Limit | Value | |---|---| | Name | 3–28 characters, one name under `coritan.gg` | | Each name | One join address or [coritan.gg name](/docs/proxies/coritan-gg-names/) on the whole platform | | Bedrock clients | Minecraft Bedrock 26.50 and newer | | PROXY protocol | Minecraft Java and TCP service join addresses | | TCP service | Servers hosted here | ## Read join addresses with the API Read the join address policy with `GET /api/v1/gameproxy/policy`. It needs no token: ```bash curl https://api.coritan.com/api/v1/gameproxy/policy ``` ```json {"auto_provision_minecraft": false, "base_domain": "coritan.gg", "listen_port": 25565} ``` `base_domain` is the join domain, `listen_port` is the port Java players join on, and `auto_provision_minecraft` says whether new Minecraft servers get a join address by themselves. List your join addresses, newest first, with `GET /api/v1/gameproxy/routes`: ```bash curl https://api.coritan.com/api/v1/gameproxy/routes \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "routes": [ { "id": 42, "service_id": 0, "upstream_service_id": 1187, "fqdn": "survival-smp.coritan.gg", "subdomain": "survival-smp", "base_domain": "coritan.gg", "mode": "minecraft_java", "listen_port": null, "upstream_host": "203.0.113.10", "upstream_port": 25565, "proxy_protocol": 0, "location_id": 3, "status": "active", "display_name": "Survival SMP", "description": null, "online_motd": null, "offline_motd": null, "favicon": null, "version_name": null, "status_cache_ttl_seconds": null, "created_at": "2026-09-01T10:00:04", "custom_domain": { "linked": false, "hostname": null, "domain": null, "subdomain": null, "probe_hostname": null, "verified": false, "verification_status": null, "include_in_list": false, "expected_cname_target": "survival-smp.coritan.gg", "failed_checks": 0, "fail_threshold": 6, "last_checked_at": null, "last_verified_at": null, "last_error": null } } ] } ``` Each field says this: | Field | What it holds | |---|---| | `fqdn` | The join address. For a TCP service, players add `listen_port` after it. | | `mode` | The edition: `minecraft_java`, `minecraft_bedrock` or `tcp_port`. | | `listen_port` | The edge port of a TCP service. `null` for Java and Bedrock, which use the shared ports. | | `upstream_service_id` | The service ID of the server hosted here that the join address forwards to. `null` for an external server's join address. | | `service_id` | For an external server's join address, the external server's service ID. For a server hosted here, read `upstream_service_id` instead. | | `upstream_host`, `upstream_port` | The address the edge forwards to. | | `location_id` | The region whose edge answers for the join address. | | `status` | `active` or `suspended`. | | `display_name`, `online_motd`, `offline_motd`, `favicon`, `version_name`, `status_cache_ttl_seconds` | The server list entry ([Customise the server list entry](/docs/proxies/join-addresses/server-list/#with-the-api)). `null` means the default. | | `custom_domain` | The custom domain linked to the join address's server ([Use your own domain for a join address](/docs/proxies/join-addresses/custom-domain/#with-the-api)). | `proxy_protocol` comes back as `0` or `1`. The list also holds your external servers' join addresses, and a row for each linked custom domain: that row's `fqdn` is your hostname, such as `play.example.com`, and its `status` is `active` only while the custom domain is verified. The dashboard folds both into the join address or external server they belong to. ## API - `GET /api/v1/gameproxy/policy`: Storefront-facing effective policy (auto-provision + base domain) (https://www.coritan.com/docs/api/reference/client/game-proxy/#op-get-api-v1-gameproxy-policy) - `GET /api/v1/gameproxy/routes`: List my routes (https://www.coritan.com/docs/api/reference/client/game-proxy/#op-get-api-v1-gameproxy-routes) # Create a join address > Give a game server a name players can join with, such as survival-smp under Coritan's game domain. Source: https://www.coritan.com/docs/proxies/join-addresses/create-a-join-address/ In the dashboard: - /dashboard/proxies/game: https://www.coritan.com/dashboard/proxies/game A join address gives one of your servers hosted here a name that players type in their game, such as `survival-smp.coritan.gg`, instead of an IP address and port. Create one when a server has no join address yet, or when you want another name for it ([How join addresses work](/docs/proxies/join-addresses/)). To give a name to a server you run elsewhere, [connect it as an external server](/docs/proxies/external-servers/connect-an-external-server/) instead. ## Before you begin - You need a server hosted here ([Order a server](/docs/managed-containers/order-a-server/)). - The join address forwards to the server's primary address and port, which the **Network** card on the server's **Settings** tab shows ([Server settings](/docs/managed-containers/settings/#network)). The game must listen on that port, so make the game's port primary first if it is not ([Make another port primary](/docs/managed-containers/ports/#make-another-port-primary)). A TCP service can forward to another of the server's ports when you create it with the API ([With the API](#with-the-api)). - For a Java server with [Bedrock crossplay](/docs/managed-containers/software/bedrock-crossplay/), create a **Minecraft Java** join address. Bedrock players join at the same name, on port `19132`. ## Create the join address 1. In the dashboard, go to [Edge Proxy](https://www.coritan.com/dashboard/proxies/game) and select **New proxy…**. 2. Choose what the edge answers for: **Minecraft Java**, **Minecraft Bedrock** or **TCP service** ([Editions and ports](/docs/proxies/join-addresses/#editions-and-ports)). 3. Choose **A server hosted here**. 4. In **Join subdomain**, type the name, such as `survival-smp`. The field checks the name as you type, and shows `survival-smp.coritan.gg is available.` when it is free ([Name rules](#name-rules)). To pick a generated name instead, select **Suggest a name**, then select one of the five names it offers. Picking one also fills in **Display name** when it is empty. 5. Choose the **Server**. The list shows each server's name with its address and port. 6. Optionally, enter a **Display name** of up to 100 characters. The dashboard shows it, and you can change it later as the **Server name** on the **Server list** tab. Without one, the join address uses its subdomain. 7. Turn on **PROXY protocol** only when the server expects a PROXY protocol version 2 header on each connection ([What your server receives](/docs/proxies/join-addresses/#what-your-server-receives)). A Bedrock join address cannot use it. > [!IMPORTANT] > You cannot change **PROXY protocol** once the join address exists. A server that does not expect the header refuses every player. 8. Select **Create join address**. For a **TCP service**, the form notes that the edge gives the join address a port of its own in the server's region. You see the port once the join address exists. ## Name rules A join address's name is one label under the join domain, and must follow these rules: - It has 3–28 characters. - It uses lowercase letters, digits and hyphens, with no hyphen at either end. - It has at least one letter, and does not start with `xn--`. - No other join address, and no [coritan.gg name](/docs/proxies/coritan-gg-names/), already has it. - It is not a reserved name. The field changes what you type as you go. It turns capitals into lowercase letters, turns spaces, underscores and dots into hyphens, and drops every other character, so `Survival SMP` becomes `survival-smp`. These names are reserved: `account`, `accounts`, `admin`, `analytics`, `api`, `app`, `assets`, `auth`, `billing`, `bitbucket`, `blog`, `cdn`, `checkout`, `console`, `control`, `dashboard`, `database`, `db`, `demo`, `dev`, `development`, `dns`, `docs`, `documentation`, `email`, `files`, `ftp`, `gameproxy`, `git`, `github`, `gitlab`, `help`, `images`, `imap`, `img`, `internal`, `invoice`, `localhost`, `login`, `mail`, `media`, `metrics`, `mongo`, `mongodb`, `monitor`, `monitoring`, `mx`, `mysql`, `nameserver`, `news`, `ns` and `ns1` to `ns8`, `oauth`, `panel`, `payment`, `payments`, `play`, `pop`, `portal`, `postgres`, `postgresql`, `private`, `proxy`, `public`, `rdp`, `redis`, `remote`, `root`, `sftp`, `shop`, `smtp`, `sql`, `ssh`, `sso`, `staging`, `static`, `stats`, `status`, `store`, `support`, `svn`, `test`, `testing`, `uploads`, `vpn`, `webmail`, `wiki` and `www`. ## Result The dashboard shows `Join address survival-smp.coritan.gg created.` and opens the join address's page. Players join at `survival-smp.coritan.gg`. For a TCP service, they add the **Edge port** from the **Overview** tab after the name, such as `survival-smp.coritan.gg:61234`. ## Troubleshooting `Subdomain is already in use` : Another join address or a coritan.gg name has the name. Choose another, or select **Suggest a name**. The foot of the form reads `That subdomain is taken.` until you do. `play is reserved; choose another.` : The name is on the reserved list in [Name rules](#name-rules). Choose another. `Use 3 to 28 characters.` : The name is shorter than 3 or longer than 28 characters after the field has changed it. `Use lowercase letters, numbers and hyphens, with no hyphen at either end.` : The name starts or ends with a hyphen. Remove it. `Include a letter; a subdomain cannot be only digits.` : Add a letter to the name. `Punycode names are not accepted.` : The name starts with `xn--`. Choose another. **A server hosted here** is greyed out : The card reads `No servers on your account yet.` Order a server first ([Order a server](/docs/managed-containers/order-a-server/)), or connect a server you run elsewhere with **A server elsewhere**. **A server elsewhere** is greyed out for a TCP service : The card reads `TCP ports are available for servers hosted here only.` A TCP service join address forwards only to a server hosted here. `Could not resolve upstream for service 1187` : The server has no address yet, for example while Coritan is still setting it up. Wait until the server is running, then try again. `Another allocation took the edge listen port at the same time. Try again.` : Two TCP service join addresses asked for the same edge port at once. Select **Create join address** again. Every player is refused after you create the join address : **PROXY protocol** is on, and the server does not expect the header. Set the server to accept PROXY protocol version 2, or create another join address without it. For Paper, set `proxies.proxy-protocol: true` in `config/paper-global.yml`; for Velocity, set `haproxy-protocol = true` in `velocity.toml`. ## Related - [How join addresses work](/docs/proxies/join-addresses/) - [Customise the server list entry](/docs/proxies/join-addresses/server-list/) - [Use your own domain for a join address](/docs/proxies/join-addresses/custom-domain/) - [Connect a server you host elsewhere](/docs/proxies/external-servers/connect-an-external-server/) ## With the API Check a name with `POST /api/v1/gameproxy/availability`. It needs no token, changes the name as the dashboard's field does, and checks it against the rules and every name in use: ```bash curl -X POST https://api.coritan.com/api/v1/gameproxy/availability \ -H "Content-Type: application/json" \ -d '{"subdomain": "survival-smp"}' ``` ```json {"available": true, "subdomain": "survival-smp", "base_domain": "coritan.gg", "fqdn": "survival-smp.coritan.gg", "reason": null} ``` A name in use answers `"available": false` with the `reason` `Subdomain is already in use`. A name that breaks a rule answers with only `available`, `subdomain` and a `reason`, such as `{"available": false, "subdomain": "play", "reason": "Subdomain 'play' is reserved"}`. The `subdomain` you send must have 3–28 characters, or the call answers `422`. Ask for generated names with `POST /api/v1/gameproxy/name-suggestions`. It needs no token, and takes a `count` from `1` to `20`, `5` by default: ```bash curl -X POST https://api.coritan.com/api/v1/gameproxy/name-suggestions \ -H "Content-Type: application/json" \ -d '{"count": 3}' ``` ```json { "suggestions": [ {"display_name": "FrostHaven", "subdomain": "frosthaven482"}, {"display_name": "PixelForge", "subdomain": "pixelforge157"}, {"display_name": "CopperIsles", "subdomain": "copperisles903"} ], "base_domain": "coritan.gg" } ``` A suggestion leaves out names that other join addresses hold, but can still match a coritan.gg name. Check it with the availability call before you create the join address. Create the join address with `POST /api/v1/gameproxy/routes`: ```bash curl -X POST https://api.coritan.com/api/v1/gameproxy/routes \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "subdomain": "survival-smp", "mode": "minecraft_java", "upstream_service_id": 1187, "display_name": "Survival SMP" }' ``` ```json { "ok": true, "module_data": { "route_id": 42, "fqdn": "survival-smp.coritan.gg", "subdomain": "survival-smp", "base_domain": "coritan.gg", "mode": "minecraft_java", "listen_port": null, "upstream": "203.0.113.10:25565", "location_id": 3, "join_address": "survival-smp.coritan.gg", "ns_ready": true } } ``` `join_address` is what players type, with the edge port after the name for a TCP service. `route_id` identifies the join address in the other join address calls. The call takes these fields: | Field | Values | Default | What it sets | |---|---|---|---| | `subdomain` | 3–28 characters that follow the [name rules](#name-rules) | Required | The name under the join domain. | | `mode` | `minecraft_java`, `minecraft_bedrock` or `tcp_port` | `minecraft_java` | The edition. `tcp_port` is a TCP service. | | `upstream_service_id` | The `service_id` of one of your servers | Required | The server to forward to. `GET /api/v1/client/servers` returns each server's `service_id`. | | `upstream_port` | A port on that server | The server's primary port | For `tcp_port`, the port on the server that the edge forwards to. Java and Bedrock join addresses follow the server's primary port, so leave it out for them. | | `display_name` | Up to 100 characters | The subdomain | The join address's name in the dashboard. | | `proxy_protocol` | `true` or `false` | `false` | A PROXY protocol version 2 header on each connection. Send `false` for Bedrock. | | `real_ip` | `true` or `false` | `true`, or `false` when `proxy_protocol` is `true` | The player's address in the Java handshake ([What your server receives](/docs/proxies/join-addresses/#what-your-server-receives)). | The call also takes the server list fields, but does not save them. Set the server list entry afterwards ([Customise the server list entry](/docs/proxies/join-addresses/server-list/#with-the-api)). A name that another join address holds answers `400` with `Subdomain 'survival-smp' is already in use on coritan.gg`, and a name that breaks a rule answers `400` with the rule, such as `Subdomain cannot be only digits`. A body without `upstream_service_id` answers `400` with `upstream_service_id or upstream_host/port required`. ## API - `POST /api/v1/gameproxy/availability`: Claim check on the write primary so create/delete races cannot lie (https://www.coritan.com/docs/api/reference/client/game-proxy/#op-post-api-v1-gameproxy-availability) - `POST /api/v1/gameproxy/name-suggestions`: Name suggestions (https://www.coritan.com/docs/api/reference/client/game-proxy/#op-post-api-v1-gameproxy-name-suggestions) - `POST /api/v1/gameproxy/routes`: Order path preferred; this creates a route bound to an existing service for power users (https://www.coritan.com/docs/api/reference/client/game-proxy/#op-post-api-v1-gameproxy-routes) # Customise the server list entry > Set the name, message of the day, icon and version text players see for your server in their game's server list. Source: https://www.coritan.com/docs/proxies/join-addresses/server-list/ In the dashboard: - /dashboard/proxies/game/…/branding: https://www.coritan.com/dashboard/proxies/game - /dashboard/proxies/external/…/branding: https://www.coritan.com/dashboard/proxies/external The edge answers the server list for your join address itself, so you can change what players see there without touching your server. The **Server list** tab of a join address, or of an external server, sets the message of the day (MOTD) while your server is up and while it is down, the icon, the version text and how long the edge reuses your server's answer. Changes also apply at a [custom domain](/docs/proxies/join-addresses/custom-domain/) linked to the join address. ## Before you begin - The join address is on your [Edge Proxy](https://www.coritan.com/dashboard/proxies/game) list and is not suspended. While it is suspended, the tab shows `This route is suspended` and you cannot save. - For an external server, the join address must exist. Until Coritan sets it up, the tab shows `Not set up yet` ([How external servers work](/docs/proxies/external-servers/)). - For an icon, have a PNG file of 64 × 64 pixels, under about 64 KB. ## Change the server list entry 1. In the dashboard, go to [Edge Proxy](https://www.coritan.com/dashboard/proxies/game) and select the join address or external server. 2. Select the **Server list** tab. 3. Change any of these fields: - **Server name**: the name the dashboard shows, up to 100 characters. It cannot be empty. - **Version string**: the version text the entry carries while your server does not answer, up to 64 characters, such as `Paper 1.21`. - **While the server is online**: the MOTD players see while your server answers, in place of the server's own. Leave it empty to show the server's own MOTD. - **While the server is offline**: the MOTD players see while your server does not answer. Leave it empty to show the platform's ([What players see](/docs/proxies/join-addresses/#what-players-see)). - **Server icon**: select **Upload PNG…** and choose the file. **Clear icon** removes it. - **Status cache**: how long the edge reuses your server's answer before it asks again, from `1 second` to `10 seconds`. **Platform default** uses the platform's setting. 4. Check the **Preview** card. Choose **Online** or **Offline** to see the entry in each state. The preview's player count, `12/100`, is only an example. 5. Select **Save changes**. Each MOTD holds up to 256 characters on two lines. The game shows the first two lines only. ### Colours and styles in the MOTD The MOTD takes Minecraft's formatting codes: a section sign `§` followed by one character. Select a button under the MOTD to insert a code where the cursor is. A coloured swatch from `0` to `f` inserts a colour, **Bold**, **Italic**, **Underline**, **Strike** and **Obfuscated** insert a style, and **Reset** inserts `§r`, which ends every code before it. For example, `§6Survival SMP §7| §aSeason 3` shows `Survival SMP` in gold, the bar in grey and `Season 3` in green. > [!NOTE] > The preview also colours codes written with `&`, such as `&a`, but the game does not. Players see `&a` as typed, so use `§` codes. ## What players see Your server's answer and your fields combine like this in a Minecraft Java server list: | Part of the entry | While your server answers | While it does not answer | |---|---|---| | MOTD | **While the server is online**, or your server's own MOTD | **While the server is offline**, or the platform's | | Icon | **Server icon**, or your server's own icon | **Server icon**, or none | | Version text | Your server's own version | **Version string**, or the platform's | | Players | Your server's own count | None | Java players see the name they gave the server when they added it to their list, so **Server name** does not appear there. Minecraft Bedrock's server list shows the two MOTDs in the same way. The **Server icon** and the **Version string** do not apply to Bedrock. While your server does not answer, the edge gives Bedrock the **Server name** as the world name. ## Result The dashboard shows `Branding saved.`, and the caption under the form reads `No unsaved changes.` Players see the new entry the next time their server list refreshes. The **Server list** card on the **Overview** tab shows the new values. ## Troubleshooting `Enter a server name.` : You emptied **Server name**. Enter a name. `The icon must be 64 x 64 pixels (this one is 128 x 128).` : Resize the image to 64 × 64 pixels and upload it again. `The icon is too large; keep it under about 64 KB.` : Save the PNG with fewer colours or more compression, then upload it again. `The icon must be a PNG.` or `That file is not a PNG image.` : The file is another format, or a file with a `.png` name that is not a PNG. Export the image as a PNG. The MOTD shows `&` and a letter : The game does not read `&` codes. Replace each `&` with `§`. The version string does not show while the server is up : The list shows your server's own version while it answers, and the **Version string** only while it does not. The **Status cache** does not go back to **Platform default** : The dashboard does not save a change back to **Platform default**. Send `"status_cache_ttl_seconds": null` with the API ([With the API](#with-the-api)). The server list shows the offline MOTD while players can still join : **PROXY protocol** is on. The edge sends the header when a player joins, but not when it asks your server for its server list entry, so a server that requires the header does not answer that request. Joins keep working. On an external server, you can turn **PROXY protocol** off on the **Connection** tab and on your server to bring the live entry back ([Connect a server you host elsewhere](/docs/proxies/external-servers/connect-an-external-server/)). ## Related - [How join addresses work](/docs/proxies/join-addresses/#what-players-see) - [Use your own domain for a join address](/docs/proxies/join-addresses/custom-domain/) - [Customise the game proxy messages](/docs/organizations/integrations/game-proxy/) ## With the API Change the server list entry with `PATCH /api/v1/gameproxy/routes/{route_id}`. `route_id` is the join address's `id` from `GET /api/v1/gameproxy/routes`, or an external server's `route_id` ([How external servers work](/docs/proxies/external-servers/)). ```bash curl -X PATCH https://api.coritan.com/api/v1/gameproxy/routes/42 \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "online_motd": "§6Survival SMP\n§7Season 3 is live", "offline_motd": "§cBack soon", "version_name": "Paper 1.21", "status_cache_ttl_seconds": 5 }' ``` ```json {"ok": true, "route_id": 42} ``` Send only the fields you change. A field you send as `null` goes back to its default, and so does one you empty with a `clear_*` field. | Field | Values | Default | What it sets | |---|---|---|---| | `display_name` | Up to 100 characters | The subdomain | **Server name** | | `online_motd` | Up to 256 characters | None: your server's own MOTD | **While the server is online** | | `offline_motd` | Up to 256 characters | None: the platform's | **While the server is offline** | | `favicon` | A `data:image/png;base64,` URI of up to 120,000 characters | None | **Server icon** | | `version_name` | Up to 64 characters | None: the platform's | **Version string** | | `status_cache_ttl_seconds` | `1`–`10` | None: the platform's | **Status cache** | | `clear_online_motd` | `true` or `false` | `false` | With `true`, empties **While the server is online**. | | `clear_offline_motd` | `true` or `false` | `false` | With `true`, empties **While the server is offline**. | | `clear_favicon` | `true` or `false` | `false` | With `true`, removes the icon. | The API checks only that `favicon` is a PNG data URI and not too long, so make the image 64 × 64 pixels yourself, as the game expects. A join address that is not on your account answers `404` with `Route not found`. A `favicon` that is too long answers `400` with `Favicon too large (max ~64KB PNG as data URI)`, and one in another format answers `400` with `Favicon must be a data:image/png;base64,... URI`. A value outside its range, such as a `status_cache_ttl_seconds` of `30`, answers `422`. ## API - `PATCH /api/v1/gameproxy/routes/{route_id}`: Update my route branding (https://www.coritan.com/docs/api/reference/client/game-proxy/#op-patch-api-v1-gameproxy-routes-route-id) # Use your own domain for a join address > Let players join a game server at a domain you own, such as play.example.com, with a CNAME record. Source: https://www.coritan.com/docs/proxies/join-addresses/custom-domain/ In the dashboard: - /dashboard/proxies/game/…/domain: https://www.coritan.com/dashboard/proxies/game - /dashboard/proxies/external/…/domain: https://www.coritan.com/dashboard/proxies/external A *custom domain* lets players join a game server at a name you own, such as `play.example.com`, as well as at its join address. You point the name at the join address with a `CNAME` record, and Coritan checks the record before the name starts to work. Each server has at most one custom domain, and so does each external server. ## Before you begin - You need a **Minecraft Java** or **Minecraft Bedrock** join address ([Create a join address](/docs/proxies/join-addresses/create-a-join-address/)), or an [external server](/docs/proxies/external-servers/). A **TCP service** join address cannot take a custom domain. - The server must not be suspended. While it is, the **Custom domain** tab shows the domain, but you cannot change it. - You need to be able to add DNS records for the domain. When the domain's DNS zone is on your Coritan account, or the name is under a `coritan.gg` name you hold, Coritan writes the record for you. ## Choose the name You enter the name in two parts: the **Root domain**, such as `example.com`, and a **Label** in front of it. | Label | Players join at | Record Coritan checks | |---|---|---| | `play`, or another single label | `play.example.com` | A `CNAME` from `play.example.com` to the join address | | `@` | The root domain itself, such as `example.com` | The root's own record ([Link the root of a domain](#link-the-root-of-a-domain)) | | `*` | Any name one level below the root domain, such as `survival.example.com` | A `CNAME` from `_coritan-verify.example.com` to the join address | A label takes lowercase letters, digits and hyphens, with no hyphen at either end, and up to 63 characters. The whole name can be up to 253 characters. To link a name deeper in your domain, such as `mc.example.com`, enter `example.com` in **Root domain** and `mc` in **Label**. You can link a name under `coritan.gg` only below a name you hold. With `alex.coritan.gg` claimed, you can link `play.alex.coritan.gg` ([Claim a free coritan.gg name](/docs/proxies/coritan-gg-names/)). ### Link the root of a domain DNS does not allow a `CNAME` at the root of a zone, such as `example.com`. Some DNS providers can flatten one there, under names such as CNAME flattening, `ALIAS` or `ANAME`: the provider looks up the join address and publishes its addresses at the root. Coritan accepts the root when every address it finds there belongs to Coritan's edge. You cannot link the root of a DNS zone on your Coritan account, or of a `coritan.gg` name you hold. Link a label such as `play` there instead. ### Link a wildcard A wildcard covers every name one level below the root domain: `*.example.com` covers `survival.example.com`, but not `lobby.survival.example.com`. Coritan cannot look up a wildcard name, so it checks `_coritan-verify.example.com` instead. Publish both records that the tab lists. ## Link a domain 1. Open the join address or external server from [Edge Proxy](https://www.coritan.com/dashboard/proxies/game), and select the **Custom domain** tab. **Link one** on the **Overview** tab opens it too. 2. In **Root domain**, enter your domain, such as `example.com`. The names you hold under `coritan.gg` are listed below the field. Select one to fill in the field, with `play` in **Label**. 3. In **Label**, enter `play`, another label, `@` or `*` ([Choose the name](#choose-the-name)). 4. At your DNS provider, add the record that the card lists under **Publish this record, then link**, with the **Type**, **Name** and **Value** shown. At most DNS providers, you enter only the part before your domain, such as `play`, as the record's name. You can skip this step when Coritan writes the record for you. For a `coritan.gg` name you hold, the card is titled **Link a name you hold here** and lists the record under **Record created when you link**. For a domain whose DNS zone is on your Coritan account, the card still asks you to publish the record, but Coritan writes it when you link. 5. Select **Link domain**. Coritan looks for the record at once, which can take a few seconds. **Use this hostname in the public server list** chooses which address a public list of servers shows for your server: the domain, once it is verified, or the join address. coritan.com does not publish a list of servers, so the box changes nothing for a server on your coritan.com account. ## Result The tab shows the domain with a badge: - `Verified`: Coritan found the record, and players can join at the domain now. When Coritan wrote the record for you, the message reads `play.example.com linked. The CNAME was written in your zone.` - `Pending`: Coritan saved the domain but cannot see the record yet. The message reads `play.example.com saved; it links once its CNAME is visible.`, and the tab shows `Waiting for the DNS record`. DNS changes can take up to an hour to reach everyone. Coritan checks every few minutes and verifies the domain when the record appears. Select **Check now** to check at once. Players join at the domain the way they join at the join address: Java players type `play.example.com`, and Bedrock players add port `19132`. The join address keeps working. On [Edge Proxy](https://www.coritan.com/dashboard/proxies/game), the domain appears under the join address, with `CNAME pending` until it is verified. When the domain's DNS zone is on Coritan, Coritan also issues a certificate for the name once it is verified, and Bedrock players join without a prompt. With the zone at another DNS provider, a Bedrock player's game can ask them to trust the server the first time they join. ## Keep the record published Coritan checks the record every few minutes for as long as the domain is linked. **Last seen** shows when it last found the record, and **Last checked** shows when it last looked. **Record to keep published** lists the record to leave in place. A single missed check changes nothing. After 6 checks in a row miss the record, the badge shows `CNAME lost`, the tab shows `The CNAME record is gone`, and players can no longer join at the domain. The join address keeps working. On [Edge Proxy](https://www.coritan.com/dashboard/proxies/game), the row shows `CNAME lost` and counts under **Needs attention**. Coritan goes on checking a lost domain. It returns to `Verified` at the first check that finds the record again, or when you select **Check now** after you publish it. ## Unlink a domain 1. On the **Custom domain** tab, select **Unlink domain…**. 2. Type the hostname, such as `play.example.com`, to confirm, and select **Unlink domain**. > [!WARNING] > Players who join at the domain can no longer reach the server once you unlink it. The join address is not affected. The record stays in your DNS, whether you published it or Coritan wrote it. Delete it there if you no longer need it ([Add, edit and delete DNS records](/docs/websites/dns/manage-dns-records/)). You can link the domain again later. To link a different domain, unlink this one first. When you delete a server, or an external server's plan ends, Coritan unlinks its domain, and anyone can link the name again. ## Troubleshooting **Link domain** stays greyed out : **Root domain** is empty or is not a full domain such as `example.com`, or **Label** is not `@`, `*` or a single label ([Choose the name](#choose-the-name)). Correct the field, and the button becomes available. `coritan.gg domains can't be linked as custom domains` : The name is under `coritan.gg`, and you do not hold the name above it. Claim a name and link a label under it, such as `play.alex.coritan.gg` ([Claim a free coritan.gg name](/docs/proxies/coritan-gg-names/)), or link a domain of your own. The message names a different join domain when your name is under that one. `A CNAME can't sit at the root of a zone hosted here. Use a subdomain such as play.` : The name is the root of a DNS zone on your Coritan account, or of a `coritan.gg` name you hold, or it is a deeper name you entered with `@` as its label. Enter a label such as `play`. For a name such as `mc.example.com`, enter `example.com` in **Root domain** and `mc` in **Label**. `This server has no join address to point a domain at` : The server has no active **Minecraft Java** or **Minecraft Bedrock** join address. Its only join address may be a **TCP service**, or the server may be suspended. [Create a join address](/docs/proxies/join-addresses/create-a-join-address/) for Java or Bedrock and link the domain there. `That hostname is already linked to another server` : Another server has this name as its custom domain. Unlink it there first. If none of your servers has it, [contact support](/docs/support/). `That hostname is already in use` : A join address already answers at this name. Choose another name, or [contact support](/docs/support/) if the name is yours. The badge stays `Pending` : Coritan cannot see a `CNAME` from the name in **Hostname** to the name in **Points at**. Check the record's name at your DNS provider, and check that it is a `CNAME` to the name in **Points at**. For a server with more than one join address, that is the join address you created first. If your DNS provider can send traffic through its own network, turn that off for this record so that lookups return the `CNAME` itself. Then select **Check now**. When the tab shows `The join address changed`, point the record at the join address that the message names. The badge stays `Pending` for a domain whose DNS zone is on your Coritan account : The name already had an `A`, `AAAA` or `CNAME` record, so Coritan did not write its own. Replace that record with a `CNAME` to the name in **Points at** ([Add, edit and delete DNS records](/docs/websites/dns/manage-dns-records/)), then select **Check now**. The badge shows `Verified`, then `CNAME lost`, for a domain whose DNS zone is on your Coritan account : Coritan wrote the record in the zone, but the domain does not use Coritan's nameservers yet, so nobody else can see the record. Change the domain's nameservers at your registrar ([Add a domain you already own](/docs/websites/add-an-existing-domain/)). The badge shows `CNAME lost` : 6 checks in a row did not find the record ([Keep the record published](#keep-the-record-published)). Publish the record under **Record to keep published** again, then select **Check now**. Players see `This proxy route is currently suspended.` : Players' DNS already finds your record, but Coritan has not verified the domain yet, or has marked it `CNAME lost`. Select **Check now**, or wait for the next check. Players see `There is no proxy associated with this domain.` : The record points at the join address, but no custom domain with this name is linked. Link it on the **Custom domain** tab. With a wildcard, the name players typed may be more than one level below the root domain. Java players reach a different server : An `SRV` record, such as one for `_minecraft._tcp.play.example.com`, still points somewhere else. Java looks that record up before the `CNAME`, so delete it. `Too many requests for this action. Please wait and try again.` : You linked a domain, or selected **Check now**, more than 12 times in an hour for this server. Wait up to an hour. Coritan keeps checking the domain meanwhile. ## Related - [How join addresses work](/docs/proxies/join-addresses/) - [How external servers work](/docs/proxies/external-servers/) - [Claim a free coritan.gg name](/docs/proxies/coritan-gg-names/) - [Add, edit and delete DNS records](/docs/websites/dns/manage-dns-records/) ## With the API Each request comes in two forms, with the same bodies and answers: - For a join address, `/api/v1/gameproxy/routes/{route_id}/custom-domain`, where `route_id` is the join address's `id` ([Read join addresses with the API](/docs/proxies/join-addresses/#read-join-addresses-with-the-api)). - For an external server, `/api/v1/client/external-servers/{service_id}/custom-domain`, where `service_id` is the external server's `id` ([Read external servers with the API](/docs/proxies/external-servers/#read-external-servers-with-the-api)). Link a domain with `POST`: ```bash curl -X POST https://api.coritan.com/api/v1/gameproxy/routes/42/custom-domain \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"domain": "example.com", "subdomain": "play"}' ``` | Field | Value | |---|---| | `domain` | Required. The root domain, such as `example.com`, in 1–255 characters. | | `subdomain` | The label: a single label such as `play`, `@` for the root domain itself or `*` for a wildcard. Up to 63 characters. Defaults to `@`. | | `include_in_list` | Whether a public server list shows the domain instead of the join address, once the domain is verified ([Link a domain](#link-a-domain)). Defaults to `false`. | The answer describes the domain: ```json { "linked": true, "hostname": "play.example.com", "domain": "example.com", "subdomain": "play", "probe_hostname": "play.example.com", "verified": false, "verification_status": "pending", "include_in_list": false, "expected_cname_target": "survival-smp.coritan.gg", "failed_checks": 0, "fail_threshold": 6, "last_checked_at": "2026-09-25T10:15:00", "last_verified_at": null, "last_error": "No CNAME from play.example.com to survival-smp.coritan.gg yet", "record_written": false } ``` | Field | What it holds | |---|---| | `linked` | `true` while a domain is linked. | | `hostname` | The name players join at. A wildcard reads `*.example.com`. | | `domain`, `subdomain` | The root domain and the label. | | `probe_hostname` | The name Coritan looks up: the hostname, or `_coritan-verify.example.com` for a wildcard. | | `verification_status` | `pending`, `verified` or `failed`. The dashboard shows `failed` as `CNAME lost`. | | `verified` | `true` while `verification_status` is `verified`. | | `include_in_list` | The public server list setting. | | `expected_cname_target` | The join address that the record must point at. | | `failed_checks`, `fail_threshold` | How many checks in a row missed the record, and how many misses turn a verified domain into `failed`. | | `last_checked_at`, `last_verified_at` | When Coritan last looked for the record, and when it last found it, in UTC. | | `last_error` | Why the last check failed, or `null`. | | `record_written` | Only in the answer to a link: `true` when Coritan wrote the record in a DNS zone on your account. | A `POST` while a domain is linked replaces it with the new one. The other requests work like this: - `GET` reads the domain. With nothing linked, `linked` is `false`, `expected_cname_target` is the join address, `fail_threshold` is `6`, and the other fields are `null`, `false` or `0`. - `POST` to `/verify` checks the record at once and answers the same shape as `GET`. - `PATCH` with `{"include_in_list": true}` or `{"include_in_list": false}` changes the public server list setting. The field is required. - `DELETE` unlinks the domain and answers the shape for nothing linked. It succeeds when nothing is linked too. Check an external server's domain now: ```bash curl -X POST https://api.coritan.com/api/v1/client/external-servers/1203/custom-domain/verify \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` A link that Coritan refuses answers `400` with one of the messages in [Troubleshooting](#troubleshooting), or with `Enter a valid root domain (e.g. example.com)`, `Subdomain must be a DNS label, '@', or '*'` or `Enter a valid hostname` for a name that breaks the rules. `/verify` and `PATCH` answer `400` with `No custom domain is linked to this server` when nothing is linked. Other errors: - `404` with `Route not found` for a join address that is not yours, or `External server not found` for an external server that is not yours. - `409` with `This route is not attached to a service, so a domain cannot be linked to it`, for a join address created with an address instead of a server. - `422` for a body without `domain`, or with a `subdomain` longer than 63 characters. - `429` for more than 12 links, or more than 12 checks, of one server's domain in an hour, as [Rate limits](/docs/api/rate-limits/) describes. ## API - `GET /api/v1/gameproxy/routes/{route_id}/custom-domain`: Vanity CNAME state for this route's join address (https://www.coritan.com/docs/api/reference/client/game-proxy/#op-get-api-v1-gameproxy-routes-route-id-custom-domain) - `POST /api/v1/gameproxy/routes/{route_id}/custom-domain`: Link route custom domain (https://www.coritan.com/docs/api/reference/client/game-proxy/#op-post-api-v1-gameproxy-routes-route-id-custom-domain) - `PATCH /api/v1/gameproxy/routes/{route_id}/custom-domain`: Show the vanity hostname, or the hosted one, in the public server list (https://www.coritan.com/docs/api/reference/client/game-proxy/#op-patch-api-v1-gameproxy-routes-route-id-custom-domain) - `DELETE /api/v1/gameproxy/routes/{route_id}/custom-domain`: Unlink route custom domain (https://www.coritan.com/docs/api/reference/client/game-proxy/#op-delete-api-v1-gameproxy-routes-route-id-custom-domain) - `POST /api/v1/gameproxy/routes/{route_id}/custom-domain/verify`: Check the CNAME now rather than waiting for the next maintenance pass (https://www.coritan.com/docs/api/reference/client/game-proxy/#op-post-api-v1-gameproxy-routes-route-id-custom-domain-verify) - `GET /api/v1/client/external-servers/{service_id}/custom-domain`: Get custom domain (https://www.coritan.com/docs/api/reference/client/external-servers/#op-get-api-v1-client-external-servers-service-id-custom-domain) - `POST /api/v1/client/external-servers/{service_id}/custom-domain`: Link custom domain (https://www.coritan.com/docs/api/reference/client/external-servers/#op-post-api-v1-client-external-servers-service-id-custom-domain) - `PATCH /api/v1/client/external-servers/{service_id}/custom-domain`: Show the vanity hostname, or the hosted one, in the public server list (https://www.coritan.com/docs/api/reference/client/external-servers/#op-patch-api-v1-client-external-servers-service-id-custom-domain) - `DELETE /api/v1/client/external-servers/{service_id}/custom-domain`: Unlink custom domain (https://www.coritan.com/docs/api/reference/client/external-servers/#op-delete-api-v1-client-external-servers-service-id-custom-domain) - `POST /api/v1/client/external-servers/{service_id}/custom-domain/verify`: Check the CNAME now rather than waiting for the next maintenance pass (https://www.coritan.com/docs/api/reference/client/external-servers/#op-post-api-v1-client-external-servers-service-id-custom-domain-verify) # How external servers work > How an external server gives a game server you host elsewhere a Coritan join address, and what its Overview tab shows. Source: https://www.coritan.com/docs/proxies/external-servers/ In the dashboard: - /dashboard/proxies/external/…/overview: https://www.coritan.com/dashboard/proxies/external An *external server* gives a Minecraft server that you run somewhere else, such as at home or with another host, a join address under `coritan.gg`. Players join at the join address, and Coritan's edge forwards their connection to your server. Your server stays where it is, and players never see its own address. You order an external server as a plan and pay for it like any other service ([Connect a server you host elsewhere](/docs/proxies/external-servers/connect-an-external-server/)). For a server hosted here, [create a join address](/docs/proxies/join-addresses/create-a-join-address/) instead. ## How players reach your server 1. The player types the join address, such as `survival-smp.coritan.gg`. It resolves to Coritan's edge in the region you chose. 2. The edge reads the name the player typed and opens a connection to your server's address and port. 3. The edge passes the player's traffic both ways. Your server sees the connection arrive from the edge. Players type only the join address, whatever port your server listens on. Java players join on the edge's port `25565`, and Bedrock players add port `19132` ([Editions and ports](/docs/proxies/join-addresses/#editions-and-ports)). When your server does not accept the connection, players see the offline message ([What players see](/docs/proxies/join-addresses/#what-players-see)). An external server carries **Minecraft Java** or **Minecraft Bedrock**, its *edition*. A Bedrock server must accept NetherNet connections, which Bedrock uses from version 26.50. The edge cannot carry players to a Bedrock server that speaks only the older RakNet protocol. A **TCP service** forwards only to a server hosted here, so an external server cannot carry one. ## Regions and edge addresses The *proxy region* is where the edges that carry your players are. You choose it when you order the external server, and you can change it on the **Connection** tab. Choose the region closest to most of your players. The list offers only the regions that have an edge available at that moment. The join address resolves only to the edges in its region, and Coritan never sends your players to another region. If no edge in the region is available, the join address stops resolving until one is. The *edge addresses* are the IP addresses of the edges in your region, and connections to your server come from them. The **Your server** card on the **Overview** tab lists them under **Edge addresses**, so that you can allow them through your server's firewall ([Set up your server](/docs/proxies/external-servers/connect-an-external-server/#set-up-your-server)). Check the list again after you change the region. ## How Coritan checks your server Coritan asks your server for its status, with the same request that a player's server list sends. It asks: - every 5 minutes, - at once after you change the server address or port, - when you select **Test connection** on the **Overview** tab. Each check waits up to 3 seconds for an answer. The badge in the external server's header shows the result: | Badge | Meaning | |---|---| | `Online` | Your server answered the last check. | | `Offline` | Your server did not answer the last check, or its answer was not a Minecraft status. | | `Not checked` | Coritan has not checked your server yet, for example just after you changed its edition. | | `Suspended` | The plan is suspended, and the join address does not resolve ([The plan behind the join address](#the-plan-behind-the-join-address)). | The check has two limits: - It does not send a PROXY protocol header. A server that requires the header does not answer the check, so the badge shows `Offline` while players can still join. The server list shows the offline message for the same reason ([Customise the server list entry](/docs/proxies/join-addresses/server-list/#troubleshooting)). - For Bedrock, it passes when the server answers the older RakNet status request. A server that speaks only RakNet shows `Online`, but players on Bedrock 26.50 and newer cannot join it through the edge. ## Addresses Coritan cannot forward to The server address must be a public IP address, or a full hostname such as `play.example.com` that resolves only to public addresses. Coritan checks it when you save it, and refuses: - a private, loopback or reserved address, such as `192.168.1.20`, and a private name, such as `server.local`; - a name without a dot, such as `minecraft`; - the address of one of Coritan's edges, because players would loop back into the network; - another customer's join address; - the join address of another external server. Enter that server's own address instead. You can enter the join address of one of your own servers hosted here. Coritan then forwards to that server's own address. [Connect a server you host elsewhere](/docs/proxies/external-servers/connect-an-external-server/#troubleshooting) lists the message for each refusal. ## The plan behind the join address An external server's join address follows its plan: - Coritan creates the join address once you pay the first invoice, and checks your server straight away. - While the plan is suspended, for example for an unpaid invoice, the join address does not resolve, and you cannot change the connection. Both come back when the plan is active again ([Failed payments and suspended services](/docs/billing/failed-payments/)). - When the plan ends, Coritan deletes the join address and unlinks its custom domain, and anyone can take the name. Coritan does not touch your own server. [Manage an external server's plan](/docs/proxies/external-servers/billing/) covers the price, plan changes and cancelling. ## The Overview tab Open the external server from the **Game** tab of [Edge Proxy](https://www.coritan.com/dashboard/proxies/game). Its row there shows the address it forwards to, with a note such as `External · Frankfurt` that names its region. The **Needs attention** count includes an external server whose check shows `Offline`. The page's header shows the server's name, badges for its status, its edition and its last check, the join address with a copy button, the address it forwards to, the region and the service number, such as `#1203`. Until Coritan creates the join address, the header reads `No join address yet`. The **Overview** tab has four cards. **Route** : The **Join address**, the **Edition**, the address it **Forwards to**, the **Region**, the **Port** players join on, whether **PROXY protocol** is on, the **Status**, the **Status cache** and when it was **Created**. **Change** opens the **Connection** tab. For a Bedrock server, the **Port** row shows the Java port; Bedrock players join on `19132`. **Your server** : What your server answered at the last check: the **Latency**, the **Players** online and the maximum, the **Version** and the **MOTD**. When your server did not answer, the card shows `The backend did not answer` with the reason. The **Edge addresses** follow, and **Test connection** checks your server at once. **Server list** : The **Name**, **Online MOTD**, **Offline MOTD**, **Version string** and **Icon** that players see in their server list. **Edit** opens the **Server list** tab ([Customise the server list entry](/docs/proxies/join-addresses/server-list/)). **Custom domain** : The domain linked to the join address, and whether it is verified. **Link one** or **Manage** opens the **Custom domain** tab ([Use your own domain for a join address](/docs/proxies/join-addresses/custom-domain/)). The **Connection** tab changes where the join address forwards ([Change the connection](/docs/proxies/external-servers/connect-an-external-server/#change-the-connection)), and the **Billing** tab holds the plan ([Manage an external server's plan](/docs/proxies/external-servers/billing/)). ## Limits | Limit | Value | |---|---| | Editions | Minecraft Java and Minecraft Bedrock | | Bedrock clients | Minecraft Bedrock 26.50 and newer, over NetherNet | | Server address | A public IP address, or a full hostname of up to 253 characters | | Port | 1–65535 | | Server name | 1–100 characters | | PROXY protocol | Minecraft Java only | | Custom domains | One for each external server | | Status checks | Every 5 minutes, each waiting up to 3 seconds | | Connection tests | 20 a minute for your account | | Connection changes | 30 in 5 minutes for each external server | ## Read external servers with the API List your external servers, newest first, with `GET /api/v1/client/external-servers`. The answer is an array, and holds every external server on your account that has not been cancelled or deleted, including one that is still waiting for payment: ```bash curl https://api.coritan.com/api/v1/client/external-servers \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json [ { "id": 1203, "service_id": 1203, "route_id": 57, "name": "Survival SMP", "hostname": null, "billing_status": "active", "billing_cycle": "monthly", "amount": 5.0, "next_due_date": "2026-10-01T00:00:00+00:00", "ordered_at": "2026-09-01T09:58:40+00:00", "module_name": "external_server", "status": "active", "state": "online", "display_name": "Survival SMP", "fqdn": "survival-smp.coritan.gg", "join_address": "survival-smp.coritan.gg", "subdomain": "survival-smp", "base_domain": "coritan.gg", "mode": "minecraft_java", "bedrock_bridge": false, "proxy_protocol": false, "real_ip": true, "upstream": {"host": "203.0.113.10", "port": 25565}, "region": {"id": 3, "name": "Frankfurt"}, "edge_addresses": [], "probe": { "checked_at": "2026-09-25T10:15:03+00:00", "ok": true, "latency_ms": 18, "players_online": 12, "players_max": 100, "version_name": "Paper 1.21.8", "motd": "Survival SMP", "error": null }, "branding": { "online_motd": null, "offline_motd": null, "favicon": null, "version_name": null, "status_cache_ttl_seconds": null }, "created_at": "2026-09-01T10:00:04+00:00", "updated_at": "2026-09-20T08:12:44+00:00" } ] ``` Each field says this: | Field | What it holds | |---|---| | `id`, `service_id` | The external server's service ID. The other external server calls take it as `service_id`. | | `route_id` | The join address's `id`. Change the server list entry with it ([Customise the server list entry](/docs/proxies/join-addresses/server-list/#with-the-api)). `null` until the join address exists. | | `name`, `display_name` | The server's name. | | `billing_status` | The plan's status: `pending` until you pay, `provisioning` while Coritan sets it up, `active`, `suspended`, `pending_termination` while Coritan removes it, or `failed` when setup failed. | | `billing_cycle`, `amount`, `next_due_date`, `ordered_at` | The plan's billing cycle and price, when it next renews, and when you ordered it. | | `status` | The join address's status: `active` or `suspended`. `null` until the join address exists. | | `state` | The badge: `online`, `offline`, `unknown` for `Not checked`, or `suspended`. | | `fqdn`, `join_address` | The join address, which players type. | | `subdomain`, `base_domain` | The join address's name and the join domain. | | `mode` | The edition: `minecraft_java` or `minecraft_bedrock`. | | `proxy_protocol` | Whether the edge sends a PROXY protocol header. | | `real_ip` | Whether a Java join address adds the player's address to the handshake when PROXY protocol is off ([What your server receives](/docs/proxies/join-addresses/#what-your-server-receives)). | | `bedrock_bridge` | Whether **Geyser on the same port** is on. | | `upstream` | The address and port the edge forwards to. | | `region` | The proxy region's `id` and `name`. | | `edge_addresses` | Always empty in the list. Read one external server for its edge addresses. | | `probe` | The last check: when it ran, whether it passed, and what your server answered. `ok` is `null` before the first check, and `error` says why a check failed. | | `branding` | The server list entry. `null` means the default. | Read one external server with `GET /api/v1/client/external-servers/{service_id}`. It answers the same fields, with the region's `edge_addresses` filled in and a `custom_domain` object that describes the linked domain ([Use your own domain for a join address](/docs/proxies/join-addresses/custom-domain/#with-the-api)): ```bash curl https://api.coritan.com/api/v1/client/external-servers/1203 \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` A server that is not on your account answers `404` with `External server not found`. List the regions that can take an external server with `GET /api/v1/client/external-servers/regions`: ```bash curl https://api.coritan.com/api/v1/client/external-servers/regions \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "regions": [ {"id": 3, "code": "fra", "name": "Frankfurt", "country_code": "DE", "nodes": 2} ], "default_region_id": 3 } ``` The list holds only the regions with at least one edge available, and `nodes` counts those edges. `default_region_id` is the first region in the list, which the order page picks for you, or `null` when no region is available. Pass a region's `id` as `location_id` when you change the connection ([Connect a server you host elsewhere](/docs/proxies/external-servers/connect-an-external-server/#with-the-api)). ## API - `GET /api/v1/client/external-servers`: List external servers (https://www.coritan.com/docs/api/reference/client/external-servers/#op-get-api-v1-client-external-servers) - `GET /api/v1/client/external-servers/{service_id}`: Get external server (https://www.coritan.com/docs/api/reference/client/external-servers/#op-get-api-v1-client-external-servers-service-id) - `GET /api/v1/client/external-servers/regions`: Regions a new or existing external server can be served from (https://www.coritan.com/docs/api/reference/client/external-servers/#op-get-api-v1-client-external-servers-regions) # Connect a server you host elsewhere > Order a join address for a game server you run elsewhere, tell Coritan where it listens and which region carries its players, and test it. Source: https://www.coritan.com/docs/proxies/external-servers/connect-an-external-server/ In the dashboard: - /dashboard/order/external-server: https://www.coritan.com/dashboard/order/external-server - /dashboard/proxies/external/…/connection: https://www.coritan.com/dashboard/proxies/external Connect a Minecraft server that you run somewhere else, such as at home or with another host, to give it a join address under `coritan.gg`. You order an *external server* as a plan. You tell Coritan where your server listens and which region carries its players, and Coritan creates the join address once you pay. This page covers the order, the connection settings, testing the connection and what your server must allow ([How external servers work](/docs/proxies/external-servers/)). For a server hosted here, [create a join address](/docs/proxies/join-addresses/create-a-join-address/) instead. ## Before you begin - Your server runs Minecraft Java, or Minecraft Bedrock that accepts NetherNet connections, which Bedrock uses from version 26.50 ([How external servers work](/docs/proxies/external-servers/#how-players-reach-your-server)). - Your server has a public IP address, or a hostname that resolves only to public addresses, and the internet can reach it on its port. On a home connection, forward the port on your router to the machine that runs the server. - For the first invoice, credit on your account pays first. Otherwise you pay by card or PayPal on the page the order lands on ([How the first payment works](/docs/get-started/order-a-service/#how-the-first-payment-works)). ## Order an external server Open the order page in one of these ways: - On the [Edge Proxy](https://www.coritan.com/dashboard/proxies/game) page, select **New proxy…**, choose **Minecraft Java** or **Minecraft Bedrock**, then choose **A server elsewhere**. That card shows the plan's starting price. The order page opens set to the edition you chose. - On [**Order a service**](https://www.coritan.com/dashboard/order), select the **External Server** card under **Edge Proxy**. The order page opens set to Java. The page has three numbered sections, and the summary under **Your order** sits beside them. On a phone the summary follows the sections, and **Review order** at the bottom of the screen takes you to it. 1. Under **Plan**, the External Server plan is already chosen. Choose the billing cycle with the buttons beside the heading, such as **Monthly**. A longer term names what it saves, such as `Annually · save 25%`. 2. Under **Join address**, type the name that players will type in **Join subdomain**, such as `survival-smp`. The field checks the name as you type, and shows `survival-smp.coritan.gg is available.` when it is free ([Name rules](/docs/proxies/join-addresses/create-a-join-address/#name-rules)). To pick a generated name instead, select **Suggest a name**, then select one of the names it offers. Picking one also fills in **Server name** when it is empty. 3. Optionally, enter a **Server name** of up to 100 characters. Without one, the external server takes its subdomain as its name. 4. Under **Your server**, check that the **Edition**, **Java** or **Bedrock**, matches your server. When you change it, the **Port** moves to the new edition's default port, unless you had typed a port of your own. 5. In **Server address**, enter your server's public IP address or hostname, such as `203.0.113.10` or `play.example.com`. In **Port**, enter the port it listens on. When you paste an address with its port, such as `play.example.com:25565`, the page splits it. 6. Leave the options as they are unless your server needs them ([Connection settings](#connection-settings)). 7. Under **Proxy region**, choose the region closest to most of your players. Each card shows the region's name and code, such as `Frankfurt` and `fra · Germany`. The page chooses a region for you when one has an edge available. 8. Check the summary. It lists the plan with its billing cycle, the **Join address** and the address it **Forwards to**, such as `203.0.113.10:25565`, with **Change** beside each to go back to its section. The **Total** shows the price and what is due today, and the line under it says how the first invoice is paid. 9. Select **Connect server** under the summary. While something is missing, the button is greyed out and the line under it names what, such as `Enter a subdomain.` If we refuse the order, **Could not place the order** appears above the button with the reason, and your choices stay as they were. Once you place it, the order's own page opens. When its title is **Order placed, payment due**, pay under **Pay invoice** with account credit, a saved card or PayPal account, or a new card ([Pay and follow the order](/docs/get-started/order-a-service/#pay-and-follow-the-order)). To pay later, leave the page and pay the invoice under [Invoices](/docs/billing/invoices/). Coritan sets the external server up once the invoice is paid. ## Connection settings **Edition** : **Java** for Minecraft Java Edition, or **Bedrock** for Minecraft Bedrock. The edition decides who can join and on which port ([Editions and ports](/docs/proxies/join-addresses/#editions-and-ports)). **Server address** : Your server's public IP address, or a full hostname of up to 253 characters. Coritan refuses private and reserved addresses, its own edges and other customers' join addresses ([Addresses Coritan cannot forward to](/docs/proxies/external-servers/#addresses-coritan-cannot-forward-to)). **Port** : The port your server listens on, from `1` to `65535`. It starts at `25565` for Java and `19132` for Bedrock. Players still join on the edge's own port, whatever you enter here. **PROXY protocol** : Off by default, and for Java only. When it is on, the edge starts each connection with a PROXY protocol version 2 header that carries the player's address. Turn it on only when your server expects the header, or every player is refused ([Set up your server](#set-up-your-server)). **RealIP in the handshake** : Shown for Java while **PROXY protocol** is off. The edge adds the player's address to the server address in the handshake, where a plugin can read it ([What your server receives](/docs/proxies/join-addresses/#what-your-server-receives)). Turn it off when your own proxy in front of the server stops matching its forced hosts. Clearing it on the order page has no effect: a new external server starts with it on, so clear it on the **Connection** tab once the server is set up. **Geyser on the same port** : Java only. It marks a Java server that also answers Bedrock players through Geyser on the same address and port, and the edition badge then reads `Java + Geyser`. The edge does not carry Bedrock players to an external server through Geyser, so Bedrock players cannot join it, even with this on. **Proxy region** : The region whose edges carry your players ([Regions and edge addresses](/docs/proxies/external-servers/#regions-and-edge-addresses)). The list offers only the regions that have an edge available. ## Change the connection 1. Open the external server from the **Game** tab of [Edge Proxy](https://www.coritan.com/dashboard/proxies/game), and select the **Connection** tab. **Change** on the **Route** card of the **Overview** tab opens it too. 2. Change the **Server name**, the connection settings or the **Proxy region**. The region list shows each region's name and code, such as `Frankfurt (fra)`. When your current region has no edge available, its entry ends in `(no edges available right now)`. 3. Select **Save changes**. The dashboard shows `Settings saved.` When you change the server address or port, Coritan checks your server at once, and the badge in the header shows the result. After you change only the edition, the badge shows `Not checked` until the next check. A custom domain linked to the join address follows every change. The **Activity** table on the **Billing** tab lists each change as `Config changed`, with `External server settings changed` as its details. While the plan is suspended, the tab shows `This server is suspended`, and you cannot change the connection until the plan is active again ([Failed payments and suspended services](/docs/billing/failed-payments/#a-service-is-suspended)). ## Test the connection 1. Open the external server's **Overview** tab. 2. On the **Your server** card, select **Test connection**. When your server answers, the dashboard shows a message such as `Backend answered in 18 ms.`, and the card shows the **Latency**, **Players**, **Version** and **MOTD**. When it does not answer, the dashboard shows the reason, and the card shows `The backend did not answer` with the same reason ([Troubleshooting](#troubleshooting)). Your account can run 20 tests a minute. **Test connection** is greyed out while the plan is suspended. ## Set up your server - Allow the **Edge addresses** from the **Your server** card through your server's firewall, on its port. Players' connections arrive from these addresses. You can block every other address on that port, so that players reach the server only through the join address. Check the list again after you change the region. - Leave `online-mode` in `server.properties` as it is. The edge does not take part in signing players in, so your server checks each player as it always has. - Keep `prevent-proxy-connections` at `false`, its default, unless **PROXY protocol** is on. The setting compares the address that each player connects from with the one they signed in to Minecraft from. Without PROXY protocol, your server sees the edge's address instead, and refuses every player. - With **PROXY protocol** on, set your server to expect the header, or it refuses every player: - Paper: `proxies.proxy-protocol: true` in `config/paper-global.yml` - Velocity: `haproxy-protocol = true` in `velocity.toml` - Waterfall: `proxy_protocol: true` on the listener in `config.yml` - With **RealIP in the handshake** on, install a plugin that reads the player's address from the handshake if you want your server's logs and bans to see it. A server without one ignores the extra text. The **On your server** card on the **Connection** tab repeats these points. ## Result Once you pay the first invoice, Coritan creates the join address, such as `survival-smp.coritan.gg`, and checks your server. The order's page follows the setup and updates on its own. Its title becomes **Your order is ready** when the external server is active, and `Open external server` at the top of the page opens it. The external server's status moves from `pending` to `provisioning`, then to `active`. It appears on the **Game** tab of [Edge Proxy](https://www.coritan.com/dashboard/proxies/game), with a note such as `External · Frankfurt` that names its region. When your server answers the check, the badge shows `Online`, and players can join at the join address. ## Troubleshooting `Enter a public IP address; private, loopback and reserved ranges cannot be proxied to.` : The address is private, such as `192.168.1.20`. Enter your server's public address. On a home connection, that is your router's public address, with the port forwarded to the server. `Enter a public IP address or a full hostname, such as play.example.com.` : The address has no dot, such as `minecraft`. Enter the full hostname. `Enter a public hostname; private and reserved names cannot be proxied to.` : The name ends in a private suffix, such as `.local` or `.lan`. Enter a public hostname or the public IP address. `The server address has characters that are not valid in a hostname.` : A hostname has only letters, digits, hyphens and dots. Remove spaces, underscores and any other characters. `Enter a port between 1 and 65535.` : The port is out of range. Enter the port your server listens on. `Choose the region players should connect through.` : No region is chosen. When **Proxy region** says `No regions are available for this product right now.`, no region has an edge available, so try again later. Otherwise, choose a region. `That subdomain is taken.` : We cannot give you the name, and the message under **Join subdomain** says why, such as `Subdomain is already in use`. Choose another name, or select **Suggest a name**. `Could not resolve play.example.com; check the address` : The hostname has no address. Check its spelling, or enter your server's IP address. `play.example.com resolves to a private or reserved address, so we cannot proxy to it` : At least one of the name's addresses is private. Point the name at your public address, or enter the public IP address. A message that starts `That address is one of our proxy edges` : The address belongs to one of Coritan's edges, for example because you entered the IP address that your join address resolves to. Enter the address your server listens on. To forward to a server hosted here, enter that server's join address. `play.example.com is another customer's server on this network; enter the address of a server you run` : The name is another customer's join address or custom domain. Enter your own server's address. `survival-smp.coritan.gg is already a join address in front of a server; enter that server's own address` : The name is the join address of an external server. Enter the address that server listens on. `No server on this network answers to lobby.coritan.gg; check the join address, or enter the server's own address` : The name is under `coritan.gg`, but no join address has it. Check the spelling. `That region has no proxy edges available right now` : The region lost its last available edge after the form opened. Choose another region. The status is `failed` after you paid : Coritan could not set up the external server. The **Activity** table on the **Billing** tab lists `Provisioning failed` with the reason. It can be one of the address messages above, or `Subdomain 'survival-smp' is already in use on coritan.gg` when someone took the name before setup. [Contact support](/docs/support/conversations/) with the service number. The badge shows `Offline` with `No Minecraft Java server answered on that address and port` : Nothing answered at the address and port within 3 seconds. Check that the server is running, that **Port** is the port it listens on, that your router forwards the port, and that your firewall allows the **Edge addresses**. Then select **Test connection**. `The server answered, but not with a valid status:` and a reason : Something answered on the port, but it did not answer as a Minecraft Java server. Check the **Port**, and check that the **Edition** is **Java**. `No Minecraft Bedrock server answered on that address and port` : Nothing answered as a Bedrock server within 3 seconds. Check that the server is running, the **Port**, your router's port forward and your firewall. `Something answered, but it is not a Bedrock server` : Something other than a Bedrock server answers on the port, such as a Java server. Check the **Port**, and check that the **Edition** is **Bedrock**. The badge shows `Offline` with PROXY protocol on, but players can join : The check does not send a PROXY protocol header, so a server that requires the header does not answer it ([How Coritan checks your server](/docs/proxies/external-servers/#how-coritan-checks-your-server)). Players are not affected. Every player is refused : **PROXY protocol** is on, and your server does not expect the header. Turn off **PROXY protocol** on the **Connection** tab, or set your server to expect it ([Set up your server](#set-up-your-server)). The badge shows `Online`, but Bedrock players cannot join : Your server speaks only the older RakNet protocol, which the check accepts. Players on Bedrock 26.50 and newer join over NetherNet, so update the server to a version that accepts NetherNet connections. Bedrock players cannot join a Java server that runs Geyser : The edge does not carry Bedrock players to an external server through Geyser, and no setting changes that. The badge shows `Not checked` after a change : You changed the edition without changing the address or port, so Coritan cleared the last check. Select **Test connection**, or wait up to 5 minutes for the next check. **Save changes** is greyed out : Nothing has changed, the plan is suspended, or the form has a problem. The text beside the button names the problem. **A server elsewhere** is greyed out : The card reads `Not available on this account.` Your account cannot order external servers. [Contact support](/docs/support/conversations/) if you expected to. `No External Server plans yet` : The External Server plan is not in the catalogue for your account. [Contact support](/docs/support/conversations/) if you expected it. **Could not load the plans** : The plans did not load. Select **Try again**, or reload the page. `Too many requests for this action. Please wait and try again.` : You ran more than 20 tests in a minute, or changed the connection more than 30 times in 5 minutes. Wait, then try again ([Rate limits](/docs/api/rate-limits/#actions-with-their-own-budget)). ## Related - [How external servers work](/docs/proxies/external-servers/) - [Manage an external server's plan](/docs/proxies/external-servers/billing/) - [Customise the server list entry](/docs/proxies/join-addresses/server-list/) - [Use your own domain for a join address](/docs/proxies/join-addresses/custom-domain/) - [Troubleshoot proxies and join addresses](/docs/proxies/troubleshooting/) ## With the API ### Place the order Find the plan with [`GET /products/`](/docs/api/reference/client/catalog/#op-get-api-v1-products): its `module_name` is `external_server`, and each entry in its `pricing` list is a `pricing_id` you can order. Check the name first with `POST /api/v1/gameproxy/availability` ([Create a join address](/docs/proxies/join-addresses/create-a-join-address/#with-the-api)). Then place the order with [`POST /services/order`](/docs/api/reference/client/services/#op-post-api-v1-services-order), with the connection in `config`: ```bash curl -X POST https://api.coritan.com/api/v1/services/order \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "product_id": 31, "pricing_id": 88, "idempotency_key": "b7e2c1d4-5f3a-4e8b-9c6d-1a2b3c4d5e6f", "config": { "subdomain": "survival-smp", "display_name": "Survival SMP", "mode": "minecraft_java", "upstream_host": "203.0.113.10", "upstream_port": 25565, "proxy_protocol": false, "bedrock_bridge": false, "location_id": 3 } }' ``` | Key | Values | Default | What it sets | |---|---|---|---| | `subdomain` | 3–28 characters that follow the [name rules](/docs/proxies/join-addresses/create-a-join-address/#name-rules) | Required | The join address's name under `coritan.gg`. | | `display_name` | Up to 100 characters | The order's `hostname`, or else the subdomain | The server's name. | | `mode` | `minecraft_java` or `minecraft_bedrock` | `minecraft_java` | The edition. | | `upstream_host` | A public IP address, or a full hostname of up to 253 characters | Required | Your server's address. | | `upstream_port` | `1`–`65535` | Required | Your server's port. | | `proxy_protocol` | `true` or `false` | `false` | A PROXY protocol version 2 header on each connection. Java only. | | `bedrock_bridge` | `true` or `false` | `false` | **Geyser on the same port**. Java only. | | `location_id` | A region's `id` from `GET /client/external-servers/regions` | The first region | The proxy region. | The order checks only the form of `config`, and answers `422` with `{"detail": {"errors": [...]}}` when something is missing or malformed, such as `Subdomain is required`, `Server address is required` or `Port must be between 1 and 65535`. An external server takes no add-ons, so an order with `addons` answers `422` with `External Server does not take add-ons. Order each product on its own.` When `requires_payment` is `true`, pay `invoice_id` as [Pay an invoice](/docs/billing/invoices/#with-the-api) shows ([Order a service](/docs/get-started/order-a-service/#with-the-api)). Coritan checks the name, the address and the region when it sets the server up after payment. When one of those checks fails, the service's status becomes `failed`, and [`GET /services/{service_ref}/events`](/docs/api/reference/client/services/#op-get-api-v1-services-service-ref-events) lists a `provisioning_failed` event with the reason. Once the server is set up, read it as [Read external servers with the API](/docs/proxies/external-servers/#read-external-servers-with-the-api) shows. ### Update the connection Change the connection with [`PATCH /client/external-servers/{service_id}`](/docs/api/reference/client/external-servers/#op-patch-api-v1-client-external-servers-service-id). Send only the fields you want to change: ```bash curl -X PATCH https://api.coritan.com/api/v1/client/external-servers/1203 \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"upstream_host": "play.example.com", "upstream_port": 25566}' ``` | Field | Values | What it changes | |---|---|---| | `display_name` | 1–100 characters | The server's name. | | `mode` | `minecraft_java` or `minecraft_bedrock` | The edition. When PROXY protocol is on, send `"proxy_protocol": false` with `minecraft_bedrock`. | | `upstream_host` | A public IP address, or a full hostname of up to 253 characters | Your server's address. | | `upstream_port` | `1`–`65535` | Your server's port. | | `proxy_protocol` | `true` or `false` | The PROXY protocol header. Java only. | | `real_ip` | `true` or `false` | The player's address in the Java handshake while PROXY protocol is off. | | `bedrock_bridge` | `true` or `false` | **Geyser on the same port**. Coritan keeps it off for Bedrock. | | `location_id` | A region's `id` | The proxy region. It must have an edge available. | A field you leave out, or send as `null`, keeps its value, and a body with no fields changes nothing. The call answers with the external server, in the shape [Read external servers with the API](/docs/proxies/external-servers/#read-external-servers-with-the-api) describes. When the address or port changed, the answer's `probe` already holds the new check. | Status | `detail` | Cause | |---|---|---| | `404` | `External server not found` | The external server is not on your account. | | `409` | `This external server is suspended` | The plan is suspended. | | `422` | `{"errors": ["That region has no proxy edges available right now"]}` | Coritan refused the change. `errors` holds one message, such as a message from [Troubleshooting](#troubleshooting), `Name is required`, or `This external server has no join address yet` before setup has finished. | | `422` | A list of fields in `detail` | A value is out of range, such as a port above `65535` or a name longer than 100 characters. | | `429` | `"error": "rate_limited"` | More than 30 changes to the server in 5 minutes. | The API also refuses PROXY protocol on a Bedrock server, with `PROXY protocol is not available for Bedrock servers; the proxy speaks RakNet to them and cannot prepend a header`. ### Run a test Check your server now with [`POST /client/external-servers/{service_id}/test`](/docs/api/reference/client/external-servers/#op-post-api-v1-client-external-servers-service-id-test): ```bash curl -X POST https://api.coritan.com/api/v1/client/external-servers/1203/test \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "probe": { "ok": true, "latency_ms": 18, "players_online": 12, "players_max": 100, "version_name": "Paper 1.21.8", "motd": "Survival SMP", "error": null, "checked_at": "2026-09-26T10:15:03+00:00", "transport": null }, "state": "online" } ``` `state` is `online` when your server answered, and `offline` when it did not, with the reason in `error`. For Bedrock, `transport` says which protocol answered: `nethernet`, or `raknet` for a server that speaks only RakNet, which players on Bedrock 26.50 and newer cannot join. It is `null` for Java. Coritan keeps the result as the last check, without `transport`. A server that has no join address yet answers `404` with `This external server has no join address`. More than 20 tests a minute from your account answer `429`. ## API - `POST /api/v1/services/order`: Order a platform service, and any add-ons bought with it (https://www.coritan.com/docs/api/reference/client/services/#op-post-api-v1-services-order) - `PATCH /api/v1/client/external-servers/{service_id}`: Change where players are forwarded, how, and through which region (https://www.coritan.com/docs/api/reference/client/external-servers/#op-patch-api-v1-client-external-servers-service-id) - `POST /api/v1/client/external-servers/{service_id}/test`: Status-ping the backend now and record the answer (https://www.coritan.com/docs/api/reference/client/external-servers/#op-post-api-v1-client-external-servers-service-id-test) # Manage an external server's plan > See what an external server costs, change its plan or cancel it from its Billing tab. Source: https://www.coritan.com/docs/proxies/external-servers/billing/ In the dashboard: - /dashboard/proxies/external/…/billing: https://www.coritan.com/dashboard/proxies/external An external server is a plan that you pay for each billing cycle, like any other service. Its **Billing** tab shows what the plan costs and when it renews, lists what has happened to the external server, and lets you change the billing cycle or cancel. The plan decides whether the join address works: while the plan is suspended, the join address does not resolve, and when the plan ends, Coritan deletes the join address ([The plan behind the join address](/docs/proxies/external-servers/#the-plan-behind-the-join-address)). ## Before you begin - Sign in to the [dashboard](https://www.coritan.com/dashboard). - A plan change can raise an invoice, which Coritan pays from your credit first. [Add credit](/docs/billing/add-credit/) or keep a [payment method](/docs/billing/payment-methods/) ready. ## Open the Billing tab 1. Go to the **Game** tab of [Edge Proxy](https://www.coritan.com/dashboard/proxies/game) and open the external server. 2. Select the **Billing** tab. The tab has three parts: **Current plan** : The **Product**, the **Plan**, the **Price**, the **Status**, the **Next renewal**, when Coritan **Set up** the external server, and its **Service ID**. **Change plan…** moves it to another billing cycle. **Activity** : The external server's history, newest first: **When** it happened, the **Event**, the **Details** and **By** whom. It lists setup, plan changes, and each change you make to the connection or the custom domain. **Cancel service** : Ends the external server ([Cancel the external server](#cancel-the-external-server)). ## Change the plan The External Server plan has a price for each billing cycle it sells, such as monthly and annually. Changing the plan moves the external server to another billing cycle. 1. On the **Current plan** card, select **Change plan…**. 2. In **New plan**, choose the billing cycle. Each entry shows its price. 3. Select **Preview** to see what the switch costs today. 4. Select **Apply change**. [Change a service's plan](/docs/billing/change-plan/) explains the preview. Read [How the switch is billed](/docs/billing/change-plan/#how-the-switch-is-billed) before you move to a longer billing cycle, because such a switch charges the new plan's full price at once. When the new plan applies, Coritan also applies the connection settings from your original order again. If you changed the connection since you ordered, check the **Connection** tab afterwards and save your settings again ([Change the connection](/docs/proxies/external-servers/connect-an-external-server/#change-the-connection)). ## Cancel the external server > [!CAUTION] > When the external server ends, Coritan deletes its join address and unlinks its custom domain, and anyone can then take the name. Players can no longer join through it. Coritan does not touch your own server. 1. On the **Cancel service** card, select **Cancel service…**. The **Cancel this service?** dialog opens and says that the join address stops resolving. 2. Under **When**, choose **At the end of the current term** to keep the join address working until the date the dialog shows, or **Immediately** to end it now. Coritan does not refund unused time. 3. Type `cancel` and select **Cancel service**. At the end of the term, the message reads `Scheduled for end-of-term termination; service remains active until then`, and the tab shows **Cancellation scheduled** with the end date. The join address keeps working until then. When you cancel immediately, the message reads `Immediate termination queued`. The status becomes `pending_termination` while Coritan removes the external server, then `terminated`. Either way, Coritan cancels every unpaid invoice for the external server. [Cancel a service](/docs/billing/cancel-a-service/) covers the details, and you cannot undo a cancellation yourself. An external server that you ordered but never paid for stays on your account with the status `pending`. Cancel it with **Immediately**, and it ends at once with `Service cancelled before provisioning` ([Orders you have not paid for](/docs/billing/cancel-a-service/#orders-you-have-not-paid-for)). ## When the plan is suspended When an invoice for the external server stays unpaid past its due date, Coritan suspends the plan. The **Billing** tab then shows **Suspended** with the reason. While the plan is suspended: - the join address does not resolve, so players cannot join; - you cannot change the connection or test it; - the badge in the header shows `Suspended`. Pay the open invoice, and Coritan lifts the suspension ([Failed payments and suspended services](/docs/billing/failed-payments/#a-service-is-suspended)). If the invoice stays unpaid, Coritan deletes the external server as if you had cancelled it ([An email says the service will be deleted](/docs/billing/failed-payments/#an-email-says-the-service-will-be-deleted)). ## Result The **Current plan** card shows the plan and price you chose, or the **Cancellation scheduled** alert shows when the external server ends. The **Activity** table lists the plan change or the cancellation. ## Troubleshooting `No other plan to switch to` : The plan has no other billing cycle on sale, so **Change plan…** does not appear. `Service is not active` : The plan is suspended, still being set up, or has ended. Pay its open invoice to lift a suspension, then try again. **Could not preview the change** or **Could not change the plan** : The message under it gives the reason. [Change a service's plan](/docs/billing/change-plan/#troubleshooting) lists the reasons. The connection changed back after a plan change : The plan change applied the connection settings from your original order again. Open the **Connection** tab, set the address, port and other settings again, and select **Save changes**. The **Cancel service** card is missing : A cancellation is already scheduled, or the external server has ended. The **Cancellation scheduled** alert shows the end date. `Service already scheduled for end-of-term termination` : You already cancelled the external server at the end of its term. To end it sooner, [contact support](/docs/support/conversations/). **Could not cancel the service** : The cancellation failed. The message beside it gives the reason. Try again, or [contact support](/docs/support/conversations/). You want to keep an external server that is scheduled to end : [Contact support](/docs/support/conversations/) before the end date. The join address stopped resolving : The plan is suspended, or it has ended. Check the **Status** on the **Current plan** card, and pay any open invoice ([Pay an invoice](/docs/billing/invoices/)). ## Related - [How external servers work](/docs/proxies/external-servers/) - [Change a service's plan](/docs/billing/change-plan/) - [Cancel a service](/docs/billing/cancel-a-service/) - [Pay an invoice](/docs/billing/invoices/) - [Failed payments and suspended services](/docs/billing/failed-payments/) ## With the API The external server's `service_id` from the external servers API is its service ID, which the services API takes as `service_ref`. Read the plan with [`GET /services/{service_ref}`](/docs/api/reference/client/services/#op-get-api-v1-services-service-ref), and the **Activity** table with [`GET /services/{service_ref}/events`](/docs/api/reference/client/services/#op-get-api-v1-services-service-ref-events) ([Manage your services](/docs/get-started/services/#with-the-api)): ```bash curl https://api.coritan.com/api/v1/services/1203 \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` The answer's `amount` is the price per billing cycle, `next_due_date` the next renewal, and `termination_date` the end date once a cancellation is scheduled. An event of the type `config_changed` records a change to the connection or the custom domain. Preview a plan change with [`GET /services/{service_ref}/upgrade-preview`](/docs/api/reference/client/services/#op-get-api-v1-services-service-ref-upgrade-preview), and apply it with [`POST /services/{service_ref}/change-plan`](/docs/api/reference/client/services/#op-post-api-v1-services-service-ref-change-plan). Both take the new plan's `new_pricing_id` ([Change a service's plan](/docs/billing/change-plan/#with-the-api)). Cancel with [`POST /services/{service_ref}/cancel`](/docs/api/reference/client/services/#op-post-api-v1-services-service-ref-cancel). `"immediate": false`, the default, ends the external server at the end of its term: ```bash curl -X POST https://api.coritan.com/api/v1/services/1203/cancel \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"immediate": false, "reason": "Moving the server to another address"}' ``` ```json { "message": "Scheduled for end-of-term termination; service remains active until then", "immediate": false, "status": "active", "termination_date": "2026-10-01T00:00:00", "snapshot_taken": false } ``` `keep_snapshot` has no effect on an external server. [Cancel a service](/docs/billing/cancel-a-service/#with-the-api) lists the errors. # Claim a free coritan.gg name > Claim a free name under a Coritan domain such as coritan.gg, use it for your sites and servers, or release it. Source: https://www.coritan.com/docs/proxies/coritan-gg-names/ A *coritan.gg name* is a free name under `coritan.gg`, such as `alex.coritan.gg`, that Coritan lends to your account. Coritan creates a DNS zone for it on your account, so you can use it for mailboxes, a website, a game server's address or anything else you would put on a domain you own. Claim one when you want an address without buying a domain. The name needs no renewal, and stays on your account while its DNS zone exists. A `@coritan.gg` mailbox, such as `alex@coritan.gg`, is a separate thing ([Get a coritan.gg mailbox](/docs/mail/webmail/claim-a-mailbox/)). ## Before you begin - An account can hold 3 names. When yours holds 3, [release one](#release-a-name) before you claim another. - Choose a name that follows the [name rules](#name-rules). Join addresses and claimed names share the names under `coritan.gg`, so a name that a join address uses is not free. ## Claim a name 1. Open the claim form from one of these places: - The dashboard's [home page](https://www.coritan.com/dashboard), while your account holds no name: select **Claim a name** on the card that offers a free `coritan.gg` name. Right after you sign up with your own email address, the dashboard opens with the form already showing. - [Websites](https://www.coritan.com/dashboard/websites): select the button that offers a free `coritan.gg` name. Once you hold a name, open the **More website actions** menu and select **Claim another name…** instead. - A form that asks for a domain, such as a mail service's domain or a web proxy's hostname: select the claim link under the field. With no name yet, the link follows `No domain?`. 2. If the form shows **Under**, choose the Coritan domain to claim the name under. The form shows it only when Coritan offers more than one. 3. In **Name**, type the name, such as `alex`. The form checks the name as you type, and shows `alex.coritan.gg is available` when it is free ([Name rules](#name-rules)). To use a name made from your profile instead, select one of the names beside **Try**. The form offers up to 3 free names, made from your name, your company and your email address. Under the field, the form also says how many more names the account can claim. 4. Select the claim button, which reads `Claim alex.coritan.gg` once the name is available, or press Enter in **Name**. ## Name rules A name is one label under `coritan.gg`, and must follow these rules: - It has 3–32 characters. - It uses lowercase letters, digits and hyphens, with no hyphen at either end. - It has no two hyphens in a row. - No join address and no other claim already has it. - It is not a reserved name. The field changes what you type as you go. It turns capitals into lowercase letters and drops every character other than letters, digits and hyphens, so `Alex_SMP` becomes `alexsmp`. These names are reserved: `abuse`, `account`, `accounts`, `admin`, `administrator`, `api`, `app`, `apps`, `assets`, `auth`, `autoconfig`, `autodiscover`, `billing`, `blog`, `bucket`, `buckets`, `cdn`, `checkout`, `client`, `clients`, `console`, `coritan`, `dashboard`, `ddos`, `demo`, `dev`, `developer`, `developers`, `dns`, `docs`, `edge`, `example`, `ftp`, `game`, `games`, `git`, `help`, `hostmaster`, `imap`, `internal`, `invoice`, `invoices`, `jmap`, `legal`, `login`, `mail`, `minecraft`, `mobile`, `mta-sts`, `mx1`, `mx2`, `mx3`, `news`, `no-reply`, `noreply`, `ns1` to `ns8`, `oauth`, `official`, `panel`, `pay`, `payment`, `payments`, `play`, `pop`, `pop3`, `portal`, `postmaster`, `privacy`, `proxy`, `register`, `root`, `secure`, `security`, `sftp`, `shield`, `shop`, `signin`, `signup`, `smtp`, `ssh`, `sso`, `staff`, `staging`, `static`, `status`, `storage`, `store`, `support`, `sys`, `system`, `terms`, `test`, `testing`, `verified`, `verify`, `vpn`, `waf`, `webmail`, `webmaster` and `www`. Coritan can reserve more, and the API's `rules.reserved` lists every reserved name ([With the API](#with-the-api)). ## Use the name The name appears in [Websites](https://www.coritan.com/dashboard/websites) with a `coritan.gg` badge, and works like a domain you own. Forms that ask for a domain list your names beside **Your names:** or **Under your name:**, and selecting one fills in the field. | Use | Example | Page | |---|---|---| | Mailboxes | `you@alex.coritan.gg` | [Add a domain to Mail Hosting](/docs/mail/mail-hosting/add-a-domain/) | | Mail sent through SMTP Relay | `alex.coritan.gg` | [Add a sending domain to SMTP Relay](/docs/mail/smtp-relay/add-a-sending-domain/) | | A website | `alex.coritan.gg` or `www.alex.coritan.gg` | [Create a web proxy](/docs/proxies/web-proxies/create-a-web-proxy/) | | A game server | `play.alex.coritan.gg` | [Use your own domain for a join address](/docs/proxies/join-addresses/custom-domain/) | | An instance's name | `vm.alex.coritan.gg` | [Manage an instance's addresses and names](/docs/cloud-compute/networking/) | | Reverse DNS for a floating IP | `mail.alex.coritan.gg` | [Set reverse DNS for a floating IP](/docs/floating-ips/reverse-dns/) | | Records of your own | Any record the zone takes | [Add, edit and delete DNS records](/docs/websites/dns/manage-dns-records/) | Coritan publishes the mail records for a claimed name itself, so a mail domain needs no DNS step ([Mail DNS records](/docs/mail/dns-records/)). A join address can use a name under yours, such as `play.alex.coritan.gg`, but not the name itself. Coritan's nameservers answer for the name, and it cannot use other nameservers or move to a registrar ([Website settings](/docs/websites/settings/#platform-name)). Coritan signs the zone with DNSSEC and publishes its DS records in `coritan.gg`, so DNSSEC needs no step from you ([Set up DNSSEC](/docs/websites/dns/dnssec/)). Coritan also requests a wildcard certificate, such as `*.alex.coritan.gg`, which covers the name and every name directly under it ([How SSL/TLS certificates work](/docs/websites/ssl/)). ## Limits | Limit | Value | |---|---| | Names an account can hold | 3 | | Length of a name | 3–32 characters | | DNS zones an account can hold, claimed names included | 100 | | Claims | 6 an hour, for your account | | Availability checks | 60 a minute, for your account | [Rate limits](/docs/api/rate-limits/#actions-with-their-own-budget) lists the budgets for every action. ## Release a name > [!CAUTION] > Releasing a name deletes its DNS zone with every record, load balancer and origin pool in it. Mail domains, web proxies, certificates and game server addresses under the name stop working, and anyone can claim the name afterwards. You cannot undo a release. 1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites) and open the name, such as `alex.coritan.gg`. 2. Open the **Settings** tab. 3. On the **Release this name** card, select **Release name…**. 4. Type the full name, such as `alex.coritan.gg`. 5. Select **Release name**. A release does not cancel a service that used the name, such as a mail service or a web proxy. [Cancel the service](/docs/billing/cancel-a-service/) as well when you no longer need it. ## Result After a claim, the form shows **Your new name** and `alex.coritan.gg is yours`. Coritan has created the name's DNS zone, added its NS and DS records to `coritan.gg` and requested its wildcard certificate. Select a card to use the name at once: **Add mailboxes**, **Point a website at it**, **Use it for a game server** or **Edit its DNS**. **Done** closes the form. After a release, the dashboard shows `alex.coritan.gg released.` and opens the Websites list, which no longer lists the name. ## Troubleshooting `At least 3 characters` : The name is shorter than 3 characters. Add to it. `Lowercase letters, digits and hyphens; not starting or ending with a hyphen` : The name starts or ends with a hyphen. Remove it. `No double hyphens` : The name has two hyphens in a row. Remove one. `That name is reserved` : Coritan keeps the name back ([Name rules](#name-rules)). Choose another. `Already in use by a game server address` : A join address uses the name. Choose another. `Already taken` : Another account holds the name, or claimed it after the form checked it. Choose another. `Could not check the name:` followed by a reason : The availability check failed. When the reason is `Too many requests for this action. Please wait and try again.`, the account made 60 checks in the past minute. Wait a minute, then type the name again. `This account holds all 3 of its coritan.gg names. Release one from its Websites settings to claim another.` : The account holds as many names as it can. [Release one](#release-a-name) to claim another. **Claim another name…** is greyed out for the same reason. **Could not claim the name** : Coritan refused the claim. The message under the title says why, and is one of the messages on this list. `You can claim up to 3 coritan.gg names` : The account already holds 3 names. [Release one](#release-a-name) first. `Zone limit reached (100)` : The account already has 100 DNS zones, and a name needs one more. [Delete a zone](/docs/websites/dns/delete-a-dns-zone/) that you no longer use. `Too many requests for this action. Please wait and try again.` : The account tried to claim a name 6 times in the past hour. Wait, then try again. The new name does not resolve : The form said `We are still finishing the delegation from coritan.gg. The name starts resolving when that is done.` Coritan has not added the records in `coritan.gg` that point at your name. [Contact support](/docs/support/conversations/) with the name if it still does not resolve. `coritan.gg is the platform's own domain; claim a name under it first` : You entered `coritan.gg` itself in a form, such as a mail domain. Enter a name you hold, such as `alex.coritan.gg`, or a name under it. `Claim a coritan.gg name before using play.coritan.gg; it is not yours yet` : The name you entered is under `coritan.gg`, but not under a name you hold. Claim the name first, or use one under yours, such as `play.alex.coritan.gg`. **Could not release the name** : The release failed, and the message gives the reason. Try again, or [contact support](/docs/support/conversations/). A name you did not release has gone : Coritan can take a name back after a trademark or abuse complaint, which deletes its zone as a release does. [Contact support](/docs/support/conversations/) to ask why. ## Related - [Website settings](/docs/websites/settings/) - [How DNS hosting works](/docs/websites/dns/) - [Use your own domain for a join address](/docs/proxies/join-addresses/custom-domain/) - [Add a domain to Mail Hosting](/docs/mail/mail-hosting/add-a-domain/) - [Get a coritan.gg mailbox](/docs/mail/webmail/claim-a-mailbox/) ## With the API Read the domains on offer, your names, the rules and the reserved list with [`GET /client/platform-domains`](/docs/api/reference/client/platform-domains/#op-get-api-v1-client-platform-domains): ```bash curl https://api.coritan.com/api/v1/client/platform-domains \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "suffixes": ["coritan.gg"], "claims": [ { "id": 17, "zone_id": 42, "suffix": "coritan.gg", "label": "alex", "fqdn": "alex.coritan.gg", "created_at": "2026-09-16T10:24:00", "zone_status": "active", "record_count": 6 } ], "limit": 3, "remaining": 2, "rules": { "min_length": 3, "max_length": 32, "pattern": "^[a-z0-9](?:[a-z0-9-]{1,30}[a-z0-9])?$", "reserved": ["abuse", "account", "accounts", "admin"] }, "suggestions": { "web": "www.alex.coritan.gg", "mail": "alex.coritan.gg", "play": "play.alex.coritan.gg", "vm": "vm.alex.coritan.gg", "ip": "ip.alex.coritan.gg", "app": "app.alex.coritan.gg", "sftp": "sftp.alex.coritan.gg", "db": "db.alex.coritan.gg" } } ``` The example shortens `reserved`, which lists every reserved name. `id` is the claim's ID, which the release and certificate calls take, and `zone_id` is the name's zone in the DNS API. `remaining` is `null` when an account can hold any number of names. `suggestions` proposes a name for each product under the first name you claimed, and is `null` while you hold none. ### Check a name Check a name with [`GET /client/platform-domains/availability`](/docs/api/reference/client/platform-domains/#op-get-api-v1-client-platform-domains-availability): ```bash curl "https://api.coritan.com/api/v1/client/platform-domains/availability?label=alex" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json {"available": true, "fqdn": "alex.coritan.gg", "reason": null} ``` `label` takes 1–63 characters, or the call answers `422`. `suffix` is the domain to claim under, and defaults to the first one in `suffixes`. The call turns capitals into lowercase letters and trims spaces, but changes nothing else, unlike the dashboard's field. A name you cannot claim answers `"available": false` with a `reason`: one of the messages in [Troubleshooting](#troubleshooting), or `Enter a name`, `At most 32 characters` or `That suffix is not offered`. The check allows 60 calls a minute for your account. ### Create a claim Claim the name with [`POST /client/platform-domains/claims`](/docs/api/reference/client/platform-domains/#op-post-api-v1-client-platform-domains-claims). The body takes `label` and, optionally, `suffix`: ```bash curl -X POST https://api.coritan.com/api/v1/client/platform-domains/claims \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"label": "alex"}' ``` ```json { "zone_id": 42, "fqdn": "alex.coritan.gg", "label": "alex", "suffix": "coritan.gg", "created_at": "2026-09-16T10:24:00.412733+00:00", "parent_delegated": true, "dnssec": {"enabled": true, "ds_published": true}, "certificate": {"domain": "*.alex.coritan.gg", "queued": true, "job_id": 90121, "reason": null} } ``` The call answers `201`. `parent_delegated` is `false` when Coritan could not yet add the name's NS records to `coritan.gg`, and the name does not resolve until it has. `certificate.queued` is `false` with a `reason` when Coritan did not request the wildcard certificate; request it again as [Request the wildcard certificate again](#request-the-wildcard-certificate-again) shows. A refused claim answers with the reason as `detail`: | Status | When | |---|---| | `403` | `You can claim up to 3 coritan.gg names`, or `Zone limit reached (100)`. | | `409` | `Already taken` or `Already in use by a game server address`. | | `422` | The name breaks a [rule](#name-rules), or `That suffix is not offered`. | | `429` | The account sent 6 claims in the past hour ([Rate limits](/docs/api/rate-limits/#actions-with-their-own-budget)). | ### Release a claim Release the name with [`DELETE /client/platform-domains/claims/{claim_id}`](/docs/api/reference/client/platform-domains/#op-delete-api-v1-client-platform-domains-claims-claim-id). The release deletes the zone and everything in it, as [Release a name](#release-a-name) describes, and asks for no confirmation: ```bash curl -X DELETE https://api.coritan.com/api/v1/client/platform-domains/claims/17 \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json {"ok": true, "fqdn": "alex.coritan.gg"} ``` A claim ID that is not one of yours answers `404` `Claim not found`. Deleting the name's zone with the DNS API releases the name too ([Delete a DNS zone](/docs/websites/dns/delete-a-dns-zone/)). ### Request the wildcard certificate again Coritan requests the wildcard certificate when you claim a name. When that request did not go ahead, or the certificate order failed, request it again with [`POST /client/platform-domains/claims/{claim_id}/certificate`](/docs/api/reference/client/platform-domains/#op-post-api-v1-client-platform-domains-claims-claim-id-certificate): ```bash curl -X POST https://api.coritan.com/api/v1/client/platform-domains/claims/17/certificate \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json {"domain": "*.alex.coritan.gg", "queued": true, "job_id": 90188, "reason": null} ``` The call answers `202`, and the name's **SSL** tab shows the certificate once it is issued. A claim ID that is not one of yours answers `404` `Claim not found`, and a claim whose zone is gone answers `409` `The claim's zone no longer exists`. When Coritan does not queue the request, the call answers `409` with the reason: `already in progress` while an earlier request for the name is still waiting, a reason that starts with `Rate limit:` when too many certificates were issued under `coritan.gg` in the past 7 days, or `coritan.gg is not delegated to the platform's nameservers yet`. Try again later. ## API - `GET /api/v1/client/platform-domains`: Overview (https://www.coritan.com/docs/api/reference/client/platform-domains/#op-get-api-v1-client-platform-domains) - `GET /api/v1/client/platform-domains/availability`: Check availability (https://www.coritan.com/docs/api/reference/client/platform-domains/#op-get-api-v1-client-platform-domains-availability) - `POST /api/v1/client/platform-domains/claims`: Create claim (https://www.coritan.com/docs/api/reference/client/platform-domains/#op-post-api-v1-client-platform-domains-claims) - `DELETE /api/v1/client/platform-domains/claims/{claim_id}`: Delete claim (https://www.coritan.com/docs/api/reference/client/platform-domains/#op-delete-api-v1-client-platform-domains-claims-claim-id) - `POST /api/v1/client/platform-domains/claims/{claim_id}/certificate`: (Re)queue the . Fixes for web proxies that show an error, players who cannot join, custom domains that fail and external servers that do not connect. Source: https://www.coritan.com/docs/proxies/troubleshooting/ This page covers what goes wrong once a web proxy, a join address, a custom domain or an external server is running. The messages that a form shows while you create or change one are in the troubleshooting section of that task's own page. ## A web proxy shows a 502 or 504 error page When the edge cannot get an answer from your origin, visitors get an error page: `502` when the connection fails, and `504` when the origin does not answer within the origin timeout. The page names the failure, such as `Connection refused`, and shows a ray ID to quote when you [contact support](/docs/support/conversations/). The `Coritan-Error` response header carries the failure's code, such as `connect_refused`, and the `Coritan-Ray` header carries the ray ID. A client that asks for JSON, or requests a path that starts with `/api/`, gets the same facts as JSON instead of the page: ```json { "error": "Bad Gateway", "status": 502, "reason": "connect_refused", "reason_label": "Connection refused", "message": "The origin server actively refused the connection.", "domain": "www.example.com", "ray_id": "8f3a1c2b9d4e5f60" } ``` The answer also carries `hint`, `edge` and `timestamp`. To see each origin that the edge tried and how each attempt ended, turn on development mode ([Use development mode while you debug](/docs/proxies/web-proxies/settings/#use-development-mode-while-you-debug)). The failures are: `No origin configured` (`no_backends_configured`) : The web proxy has no origin to forward to. Set the **Origin host** and **Port** on the **Origin** tab ([Change a web proxy's origin](/docs/proxies/web-proxies/change-the-origin/)). `All origins marked down` (`all_origins_unhealthy`) : Every origin failed several times in a row, so the edge skips them for a short time and then tries them again. The failure named on the earlier error pages, or development mode's list of attempts, says which of the causes below applies. `Connection refused` (`connect_refused`) : Nothing listens on the origin's address and port, or a firewall refuses the connection. Check that the site's server is running, and that the **Port** on the **Origin** tab is the port it listens on. `Connect timed out` (`connect_timeout`) : The origin did not accept the connection in time. A host that is down, or a firewall that drops connections from the internet, looks like this. Check that the host is up, and that its firewall accepts connections to the port from the internet. `Origin unreachable` (`connect_unreachable`) : The edge has no network path to the origin's address. Check the address in **Origin host**. `Origin DNS failed` (`connect_dns`) : The **Origin host** is a hostname, and it did not resolve when the edge looked it up. Check the name's records at its DNS provider, or enter the origin's IP address. `Origin TLS failed` (`connect_tls`) : **Connect to the origin over TLS** is on, and the edge refused the origin's certificate or TLS version, or the port does not serve HTTPS. Give the origin a certificate for the origin host from a public certificate authority, or turn TLS off and use the origin's plain HTTP port ([Origins](/docs/proxies/web-proxies/#origins)). `Connect failed` (`connect_failed`) : The edge could not open a connection for another reason. Check that the origin is online, and that its address and port are right. `Closed before replying` (`origin_closed_early`) : The origin accepted the connection, then closed it without an answer. Something on the port does not speak HTTP, or the site's application stopped in the middle of the request. Check the origin's own logs for the time on the error page. `Connection reset` (`origin_reset`) : The origin dropped the connection while the request was in progress. A restart, a process that ran out of memory, or another proxy in front of the application looks like this. Check the origin's logs. `Response timed out` (`read_timeout`) : The origin accepted the request, but did not finish its answer within the origin timeout, which is 60 seconds unless you changed it. This is the failure that answers `504`. Look for a slow page or a slow database query, or raise **Origin timeout (s)** on the **Origin** tab, up to 120 seconds. `WebSocket refused` (`ws_rejected`) : The origin answered a WebSocket request with something other than `101`. Check that the path accepts WebSocket connections. `Edge error` (`proxy_error`) : The request failed inside Coritan's edge, and nothing on your side causes it. [Contact support](/docs/support/conversations/) with the ray ID. When every request fails just after you change the origin, check **PROXY protocol to origin** first. An origin that does not expect the header fails every request, so turn the switch off, or set the origin's web server to accept PROXY protocol version 2 ([Change a web proxy's origin](/docs/proxies/web-proxies/change-the-origin/#troubleshooting)). ## Visitors see a certificate warning The edge serves the name's own certificate once it is `active`. Until then, the edge answers HTTPS with a certificate for another name, and browsers warn visitors. The **Certificate** card on the web proxy's **Overview** tab shows where the certificate stands. The card shows **Issuance in progress** : The certificate authority is still checking the name. Reload the page in a few minutes. The card shows **The last issuance failed** : The authority could not validate the name, and the card gives its reason. The name must resolve to the edge, or be in a DNS zone that Coritan hosts. Fix that, then select **Request certificate** ([Request or renew the certificate](/docs/proxies/web-proxies/change-the-origin/#request-or-renew-the-certificate)). The web proxy has no certificate : **Request a certificate automatically** was off when you created it. Select **Request certificate** on the card. A certificate covers one name, so `example.com` and `www.example.com` each need their own ([Visitors see a certificate warning](/docs/websites/troubleshooting/#visitors-see-a-certificate-warning)). ## Visitors see "This domain is not assigned to any active service on our network" The edge answers `404` with this page when the name reaches it but has no active web proxy. No web proxy has the exact name : `example.com` and `www.example.com` are separate names, and each needs its own web proxy. Create one for the name that visitors type ([Create a web proxy](/docs/proxies/web-proxies/create-a-web-proxy/)). The web proxy is new : The edge can go on answering `404` for a few seconds after you create a web proxy. Reload the page. You deleted the web proxy : A proxied DNS record still sends visitors to the edge. Create a web proxy for the name, or turn **Proxied** off for the record ([Change a web proxy's settings](/docs/proxies/web-proxies/settings/#troubleshooting)). The service is suspended : A web proxy whose service or domain is suspended answers this page until you pay ([Failed payments and suspended services](/docs/billing/failed-payments/#a-service-is-suspended)). ## Visitors get too many redirects The browser gives up because each answer is another redirect. Your origin redirects plain HTTP to HTTPS : The edge connects to the origin over plain HTTP unless **Connect to the origin over TLS** is on. An origin that redirects plain HTTP to HTTPS then answers every request with another redirect. Set the origin to trust the `X-Forwarded-Proto` header, which the edge sets to `https` for visitors on HTTPS, or turn on TLS to the origin and use its HTTPS port ([What your origin receives](/docs/proxies/web-proxies/#what-your-origin-receives)). A redirect rule matches its own destination : The destination falls inside the rule's source path. [Redirect requests with rules](/docs/proxies/web-proxies/redirect-rules/#troubleshooting) shows how to fix the rule. ## The WAF blocks visitors or asks them to verify With the **Web application firewall** on, a request that looks malicious gets a `403` page that reads `The web application firewall blocked your request to protect this website from traffic that looks malicious.` A request that looks suspicious gets a page titled `Verify you are human` ([The web application firewall](/docs/proxies/web-proxies/#the-web-application-firewall)). A browser gets the check again and again : A browser that passes is not asked again on that web proxy for 20 minutes, as long as its IP address stays the same. A visitor whose address changes, such as on a mobile network, can be asked again. An API client or a script is blocked : Scripts cannot pass the check. You cannot change the rules or exempt a path, so turn the WAF off for the web proxy, or serve the API from a name of its own with the WAF off ([Change a web proxy's settings](/docs/proxies/web-proxies/settings/#troubleshooting)). ## Clients get 429 Too many requests The edge limits how fast one IP address can send requests. Over the limit, it answers `429` with `{"error":"Too many requests"}` and a `Retry-After` of 1 second. After 8 refused requests in a row, it refuses every request from that address for 5 minutes. The limit applies to each address on every web proxy, and you cannot change it. Slow the client down, and wait at least 1 second after a `429` before it sends again. Visitors behind one address, such as an office network, share the limit. ## Uploads fail with 413 A request body larger than the web proxy's limit gets `413` with `{"error":"Request body too large"}`. Raise **Max request body (MB)** on the **Origin** tab, up to 100 MB. A web proxy cannot pass a larger body. ## Visitors get an old copy of a file The edge keeps copies of images, fonts, audio and video for up to an hour ([Caching](/docs/proxies/web-proxies/#caching)). An answer with `X-Cache: HIT` came from the cache. You cannot clear the cache yourself, so give a changed file a new URL, such as a new file name, or [contact support](/docs/support/conversations/) to clear it. ## Players cannot find the server The player's game says it cannot find or resolve the server. The name has a typo : Players must type the join address exactly, such as `survival-smp.coritan.gg`. Copy it from the join address's page on [Edge Proxy](https://www.coritan.com/dashboard/proxies/game). The join address is suspended : The name stops resolving while the server, or the external server's plan, is suspended. It comes back with the service ([Failed payments and suspended services](/docs/billing/failed-payments/#a-service-is-suspended)). The server was deleted : Coritan deleted its join addresses with it, and the names became free for anyone to take. Create a join address for the server you use now ([Create a join address](/docs/proxies/join-addresses/create-a-join-address/)). The address needs a port : Bedrock players join on port `19132`, the game's default. For a **TCP service**, players add the **Edge port** from the **Overview** tab after the name, such as `survival-smp.coritan.gg:61234` ([Editions and ports](/docs/proxies/join-addresses/#editions-and-ports)). Players use your own domain : A linked domain works only once its badge shows `Verified` ([A custom domain does not work](#a-custom-domain-does-not-work)). ## Players see the offline message when they join A Java player gets the offline message when the edge cannot open a connection to your server within 5 seconds. The message is the **While the server is offline** text from the **Server list** tab, or `Server is currently offline` when that is empty ([What players see](/docs/proxies/join-addresses/#what-players-see)). The server is not running : Start it, and wait until its console prints the game's `Done` line ([Players cannot connect](/docs/managed-containers/troubleshooting/#players-cannot-connect)). A free server that is asleep starts when a player joins ([Sleeping and waking](/docs/managed-containers/free-servers/#sleeping-and-waking)). The game listens on another port : A join address to a server hosted here forwards to the server's primary port. Make the game's port primary ([Make another port primary](/docs/managed-containers/ports/#make-another-port-primary)). An external server does not answer : Check the address, the port, your router's port forward and your firewall, then select **Test connection** on the **Connection** tab ([Connect a server you host elsewhere](/docs/proxies/external-servers/connect-an-external-server/#troubleshooting)). ## Every player is refused through a join address Your server takes the connection from the edge, then turns the player away. **PROXY protocol** is on, and the server does not expect the header : The edge starts each connection with a PROXY protocol version 2 header, which the server cannot read. For a server hosted here, set the server to accept the header, or create another join address without it: you cannot change **PROXY protocol** once a join address exists. For an external server, turn **PROXY protocol** off on the **Connection** tab, or set your server to accept the header. `prevent-proxy-connections` is `true` in `server.properties` : The server compares the address each player connects from with the one they signed in to Minecraft from, and sees the edge's address instead. Set it to `false`, or use a join address with **PROXY protocol** on and set the server to accept the header. To make a server accept the header: - Paper: set `proxies.proxy-protocol: true` in `config/paper-global.yml`. - Velocity: set `haproxy-protocol = true` in `velocity.toml`. - Waterfall: set `proxy_protocol: true` on the listener in `config.yml`. A server that expects the header refuses players who join at its own address instead of the join address, because their connections carry no header. ## Players see a message from the edge The edge answers some joins itself, before they reach your server ([What players see](/docs/proxies/join-addresses/#what-players-see)). `There is no proxy associated with this domain.` : The name reaches the edge, but no join address or linked domain has it. Check the name, or link the domain on the join address's **Custom domain** tab ([Use your own domain for a join address](/docs/proxies/join-addresses/custom-domain/#troubleshooting)). `This proxy route is currently suspended.` : The join address is suspended, or players reach it through a linked domain that Coritan has not verified or has marked `CNAME lost`. `Please rejoin to verify your connection.` : The edge is under a flood, or the player's address has too many connections open. Join again, and the second attempt goes through. `Your IP is temporarily blocked for suspicious activity.` : The player's address opened too many connections in a short time. Wait, then join again. Players who share one address, such as on a school network, share its limit. `This proxy is at capacity. Please try again shortly.` : The edge carries all the players it can take. Join again shortly. ## The server list shows the offline message while players can join **PROXY protocol** is on. The edge sends the header when a player joins, but not when it asks your server for its server list entry, so a server that requires the header does not answer that request. Joins keep working ([Customise the server list entry](/docs/proxies/join-addresses/server-list/#troubleshooting)). ## Your server logs the same address for every player Each player's connection reaches your server from the edge's address, so logs and IP bans see that address. To pass on each player's own address, use a Java or TCP service join address with **PROXY protocol** on, or keep it off on a Java join address and install a plugin that reads the player's address from the handshake ([What your server receives](/docs/proxies/join-addresses/#what-your-server-receives)). A Bedrock join address passes on neither. ## Bedrock players cannot join The player's game is older than version 26.50 : Join addresses carry Bedrock players over NetherNet, which the game uses from version 26.50. The player must update the game. The server speaks only RakNet : A Bedrock server must accept NetherNet connections. Update the server to a version that does. The check on an external server can still show `Online` for such a server ([How Coritan checks your server](/docs/proxies/external-servers/#how-coritan-checks-your-server)). The server is a Java server with Bedrock crossplay : For a server hosted here, create a **Minecraft Java** join address, and Bedrock players join at the same name on port `19132` ([Let Bedrock players join a Java server](/docs/managed-containers/software/bedrock-crossplay/)). The edge does not carry Bedrock players to an external server through Geyser. ## A custom domain does not work The badge on the join address's **Custom domain** tab says what Coritan found. [Use your own domain for a join address](/docs/proxies/join-addresses/custom-domain/#troubleshooting) lists every message. The badge stays `Pending` : Coritan cannot see a `CNAME` from the name to the join address. Check the record at your DNS provider, then select **Check now**. If the provider can send traffic through its own network, turn that off for the record. The badge shows `CNAME lost` : 6 checks in a row did not find the record, and players can no longer join at the domain. Publish the record again, then select **Check now**. Java players reach a different server : An `SRV` record for the name, such as `_minecraft._tcp.play.example.com`, still points somewhere else. Java looks it up before the `CNAME`, so delete it. ## An external server shows `Offline` The badge shows the result of Coritan's last check of your server ([How Coritan checks your server](/docs/proxies/external-servers/#how-coritan-checks-your-server)). Select **Test connection** on the **Connection** tab to check again at once. Nothing answers : The check reads `No Minecraft Java server answered on that address and port`, or the Bedrock one. Check that the server is running, the **Port**, your router's port forward, and that your firewall allows the **Edge addresses** ([Set up your server](/docs/proxies/external-servers/connect-an-external-server/#set-up-your-server)). Something else answers : `The server answered, but not with a valid status:` or `Something answered, but it is not a Bedrock server` means the port or the **Edition** is wrong. Check both on the **Connection** tab. **PROXY protocol** is on : The check sends no PROXY protocol header, so a server that requires one does not answer it. Players can still join. ## Coritan refuses an external server's address Coritan forwards only to a public address that is yours ([Addresses Coritan cannot forward to](/docs/proxies/external-servers/#addresses-coritan-cannot-forward-to)). It refuses a private or reserved address, a hostname that does not resolve, an address of one of Coritan's own edges, and another customer's server. The address you enter is the one your server listens on, never your join address or the address it resolves to. [Connect a server you host elsewhere](/docs/proxies/external-servers/connect-an-external-server/#troubleshooting) lists each message with its fix. ## An external server's status is `failed` Coritan could not set up the external server after you paid. The **Activity** table on its **Billing** tab lists `Provisioning failed` with the reason. [Contact support](/docs/support/conversations/) with the service number. # Floating IPs > Hold public IPv4 addresses and subnets on your account and attach them to instances and servers. Source: https://www.coritan.com/docs/floating-ips/ In the dashboard: - /solutions/floating-ips: https://www.coritan.com/solutions/floating-ips - /dashboard/networking: https://www.coritan.com/dashboard/networking - /dashboard/networking/ips: https://www.coritan.com/dashboard/networking/ips - /dashboard/networking/ips/…/overview: https://www.coritan.com/dashboard/networking/ips A *floating IP* is a public IP address that belongs to your account instead of to one machine. You attach it to a Cloud Compute instance or a Container Apps server, and you can move it to another one later while the address stays the same. A floating IP is either a single IPv4 address or a *subnet*, a block of addresses whose hosts you attach one at a time. Use a floating IP when an address has to outlast the machine behind it, such as a mail server whose reverse DNS you want to keep or a game server address your players have saved. ## Your floating IPs Select **Floating IPs** in the [dashboard](https://www.coritan.com/dashboard/networking) sidebar. The **IPs** tab lists every floating IP on your account. Each row shows the **Address** with its prefix length, such as `203.0.113.10/32`, its **Version**, **Region** and **Prefix**, what it is **Attached to**, and its **Status**. A subnet shows how many usable hosts it has and how many of them are attached. - Filter the list with **All**, **Attached**, **Unattached** or **Pending**. `Pending` marks a floating IP whose service is not active, such as one that waits for payment or is suspended. - Type in the search box to find a floating IP by its address, region, pool or what it is attached to. - Select **Order floating IP** to order one, as [Order a floating IP](/docs/floating-ips/order-a-floating-ip/) describes. The IPv4 address that comes with a Cloud Compute instance is a floating IP too, so it appears in this list. It ends when the instance ends. Select a row to open the floating IP. The header shows the address, its status, whether it is an `IPv4 address` or a `Subnet block`, its region and what it is attached to. **Copy address** copies the address. ## Read the Overview tab The **Overview** tab opens first. When a single address is not attached to anything, the tab starts with **Not attached to anything yet** and a link to attach it. **Address** : The address or subnet, its **Version**, **Prefix**, **Region** and **Pool**, and its **Gateway** when it has one. A single address shows what it is **Attached to**, **Attached since** and, on an instance, its **Role on instance**: `Primary address` or `Additional address`. A subnet shows **Hosts**: how many of its usable hosts are attached and how many are free. **Reverse DNS** shows the address's hostname, or **Set reverse DNS** when it has none. **Service** : The service that bills for the floating IP: its **Status**, **Product**, **Price**, **Next renewal**, when it was **Ordered**, and its **Service ID**. **Open Billing** opens the **Billing** tab. **Tags** : Labels to find the floating IP by, as [Tag your services](/docs/get-started/tags/) describes. **Protection** : A reminder that DDoS Shield filters traffic to the address. **Open Shield** opens the **Shield** tab. ## What you can do Get an address: - [Order a floating IP](/docs/floating-ips/order-a-floating-ip/), and follow the order on the **Orders** tab - [IP pools and regions](/docs/floating-ips/pools/), the **Pools** tab Use it: - [Attach and detach a floating IP](/docs/floating-ips/attach-and-detach/), the **Attach** tab - [Set reverse DNS for a floating IP](/docs/floating-ips/reverse-dns/), the **Reverse DNS** tab - [How subnets work](/docs/floating-ips/subnets/), the **Hosts** tab - [Change a floating IP's DDoS protection](/docs/floating-ips/shield-settings/), the **Shield** tab Pay for it and fix problems: - [Cancel a floating IP](/docs/floating-ips/billing/), the **Billing** tab - [Troubleshoot floating IPs](/docs/floating-ips/troubleshooting/) The public page at `https://www.coritan.com/solutions/floating-ips` describes the product. The **Pools** tab shows the sizes and regions you can actually order. ## How floating IPs are billed Each floating IP is a service of its own, on the plan and billing cycle you order it on. A single address costs nothing while it is the primary address of a Cloud Compute instance. Anywhere else it bills at its plan's price: while it is unattached, as an additional address on an instance, or on a server. A subnet always bills at its plan's price. [Cancel a floating IP](/docs/floating-ips/billing/) explains how the price follows the address. [Billing](/docs/billing/) covers invoices, credit and payment. ## Limits and availability - Floating IPs are IPv4. You can order a single address, or a subnet from `/29` to `/24` where the **Pools** tab offers one. - A floating IP attaches to a Cloud Compute instance or a Container Apps server. - A single address attaches to one instance or server at a time. Each host of a subnet attaches on its own, so different hosts can go to different targets. - A server holds one floating IP. An instance can hold several, and one of them is its primary address. - DDoS Shield filters traffic to every floating IP on your account, attached or not. ## With the API `GET /api/v1/client/ips` lists the floating IPs on your account, newest first, as `items` with a `total`. ```bash curl https://api.coritan.com/api/v1/client/ips \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "items": [ { "service_id": 214, "service_status": "active", "amount": 4.0, "billing_cycle": "monthly", "next_due_date": "2026-10-25T00:00:00", "kind": "standalone", "address": "203.0.113.10", "cidr": "203.0.113.10/32", "version": "ipv4", "prefix_len": 32, "inventory_status": "assigned", "pool_id": 4, "pool_name": "Frankfurt 1", "region": "fra", "geo_label": "Frankfurt", "target_type": "vps", "target_service_id": 118, "attached_at": "2026-09-25T10:00:00", "is_primary": 1 } ], "total": 1 } ``` Use `service_id` in every other floating IP request. `kind` is `standalone` for a single address and `block` for a subnet. `inventory_status` is `assigned` once the address is yours to attach. `amount` is what the next renewal costs. For a single address, `target_type` is `vps` for an instance or `container` for a server, and `target_service_id` is that service's ID. Both are `null` when the address is not attached. `is_primary` is `1` when the address is its instance's primary address. On a server, `host_state` and `host_error` say whether the address reached the server's host. A subnet has `null` in the attachment fields. It carries `hosts`, one entry for each address in the block, and the counts `hosts_total`, `hosts_attached` and `hosts_available`, as [How subnets work](/docs/floating-ips/subnets/) describes. `GET /api/v1/client/ips/{service_id}` returns one floating IP with the same fields. A service that is not a floating IP on your account answers `404` with `IP service not found`. The [Floating IPs API reference](/docs/api/reference/client/floating-ips/#op-get-api-v1-client-ips) lists every field. ## API - `GET /api/v1/client/ips`: List account-owned floating IP / subnet services and attachment state (https://www.coritan.com/docs/api/reference/client/floating-ips/#op-get-api-v1-client-ips) - `GET /api/v1/client/ips/{service_id}`: Get my IP (https://www.coritan.com/docs/api/reference/client/floating-ips/#op-get-api-v1-client-ips-service-id) # Order a floating IP > Order a single IPv4 address or a subnet from a region's pool, pay for it on the order's page, and follow or cancel an open order. Source: https://www.coritan.com/docs/floating-ips/order-a-floating-ip/ In the dashboard: - /dashboard/order/floating-ips: https://www.coritan.com/dashboard/order/floating-ips - /dashboard/networking/orders: https://www.coritan.com/dashboard/networking/orders Order a floating IP when you need an IP address that stays on your account as instances and servers come and go. The **Floating IPs** order page asks for the size and the region the address comes from, and can add a DDoS Shield profile to the same order. We hold an address for you while you pay, then assign it to your account. To get addresses with a new instance or server, add them on its own order page instead: [Create an instance](/docs/cloud-compute/create-an-instance/#add-products-to-the-order) takes up to four floating IPs, and [Order a server](/docs/managed-containers/order-a-server/#add-products-to-the-order) takes one dedicated address. ## Before you begin - Know which region your instance or server runs in. We announce a floating IP from its pool's region, so choose a pool in the same region. [IP pools and regions](/docs/floating-ips/pools/) explains the pools. - Credit on your account pays the first invoice when you place the order. Without enough credit, you pay the rest by card or PayPal on the page the order opens. To pay in one go, [add credit](/docs/billing/add-credit/) first. ## Place the order 1. In the [dashboard](https://www.coritan.com/dashboard/networking), go to **Floating IPs** and select **Order floating IP**. You can also select the **Floating IPs** card on the **Order a service** page. Either way, the order page opens at `https://www.coritan.com/dashboard/order/floating-ips`. 2. Under **Size**, choose a single address, or a subnet when a server needs several. Each card shows its price for the billing cycle and any one-time setup fee. A size marked **Sold out** cannot be ordered right now. 3. If the sizes sell more than one billing cycle, choose one at the top of **Size**. A longer term names what it saves against paying monthly. 4. Under **Region**, choose a pool in the region where your instance or server runs. The pools are grouped by region, and each shows its airport code, such as `FRA`. We start with a pool in your own region. A pool marked **Sold out** has no address of this size left. 5. Under **Add-ons**, you can turn on the **DDoS Shield profile** card. It gives the address your own protection mode, firewall rules and packet rate limits, and goes on the same first invoice. Give it a **Profile name**, or we name it after the order, such as `Shield profile for #215`. Without a profile, the address keeps the scrubbing every address on the platform gets. 6. Check the summary under **Your order**. It lists the **Size** and the **Region**, each with **Change** beside it, then what we bill under **Billed** and the **Total**. The line under the total says how the first invoice will be paid. 7. If the button is greyed out, read the line under it. It names the first answer still missing, such as `Choose a region.`, and selecting it takes you to that section. 8. If a verification check appears above the button, complete it. It appears when the size costs nothing, even if the add-on costs money. 9. Select the button. It reads **Place order** when your credit pays the first invoice or your account is billed in arrears, **Place order and pay** when you pay on the next page, and **Order for free** when nothing in the order costs anything. If we refuse the order, **Could not place the order** appears above the button with the reason, and your choices stay as they were. Otherwise the order's own page opens. When its title reads **Order placed, payment due**, pay under **Pay invoice**, as [Pay and follow the order](/docs/get-started/order-a-service/#pay-and-follow-the-order) describes. If you leave without paying, the order waits on the **Orders** tab, as [Follow the order](#follow-the-order) describes. To order a size from a particular pool, go to the **Pools** tab and select **Order** beside that size. The order page opens with the size and the pool chosen. ## Follow the order The order's page follows the order until the address is assigned. The **Orders** tab lists the orders you have placed that do not have an address assigned yet, under **Open orders**. Each row shows the **Pool** with the size and region, the **Status**, when it was **Placed**, and **Open invoice** when it has one. **Refresh** reloads the list. `Started` : We are placing the order. `Address held` : We have reserved an address in the pool for you. `Awaiting payment` : The order has an invoice to pay. The address stays held. `Provisioning` : The invoice is paid and we are assigning the address to your account. Under the status, the tab shows when the hold on the address expires, and an error when the last attempt failed. Pay before the hold expires. After that, we release the address back to the pool. We cancel an order whose invoice is still unpaid after two days, by default. Once we assign the address, the order leaves the **Orders** tab and the floating IP appears on the **IPs** tab. ## Cancel an open order You can cancel an order while it is `Started`, `Address held` or `Awaiting payment`. 1. On the **Orders** tab, select **Cancel order…** on the order's row. 2. Select **Cancel order**. The held address goes back to the pool and we void the unpaid invoice. Once an order is `Provisioning`, you cannot cancel it here: wait until the floating IP appears on the **IPs** tab, then [cancel the floating IP](/docs/floating-ips/billing/). ## Result The floating IP appears on the **IPs** tab as `Unattached`. It is yours, DDoS Shield filters its traffic, and you can [attach it](/docs/floating-ips/attach-and-detach/) to an instance or a server. A DDoS Shield profile ordered with it protects it as soon as both are ready. ## Troubleshooting `This size is sold out` : No pool has an address of this size left. Choose another size, or look at the **Pools** tab later. Pools refill as other customers release addresses. `Choose a region.` : Choose a pool under **Region** before you place the order. **Could not load the pools** : The list of pools did not load. Reload the page. `Complete the verification check.` : The size costs nothing, so the order needs the check above the button. Complete it, then place the order. `No available addresses in the selected pool` : The pool ran out while you were ordering. Choose another pool in the same region, or another size. `An identical IP order is already in progress; retry in a moment` : The same order is being placed from another tab or request. Wait a moment, then look for it on the **Orders** tab before you order again. The order stays at `Awaiting payment` : The invoice is not paid yet. Select **Open invoice** and pay it, as [Pay an invoice](/docs/billing/invoices/) describes. `Hold expired` under the status : We released the address you were holding. Cancel the order and place a new one. `This IP order can no longer be cancelled` : The order is paid and we are assigning the address, or it has already ended. Once the floating IP appears on the **IPs** tab, [cancel it](/docs/floating-ips/billing/) if you no longer want it. `This IP is already provisioned` : The address is already on your account. Cancel it from its **Billing** tab instead. ## Related - [IP pools and regions](/docs/floating-ips/pools/) - [How subnets work](/docs/floating-ips/subnets/) - [Attach and detach a floating IP](/docs/floating-ips/attach-and-detach/) - [Order and use a custom profile](/docs/ddos-shield/custom-profiles/) - [Pay an invoice](/docs/billing/invoices/) ## With the API An order goes through `POST /api/v1/services/order`, the endpoint every product uses. Gather three values first. 1. Find the plan. `GET /api/v1/products/` lists the catalogue. Floating IP plans have `module_name` set to `ip`, and each has a list of `pricing` rows. A row's `id` is the `pricing_id` for one billing period at its `price`. 2. Find a pool with stock of the size you want, as [IP pools and regions](/docs/floating-ips/pools/) describes: ```bash curl "https://api.coritan.com/api/v1/client/ips/pools?prefix_len=29®ion=fra" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` 3. Choose an idempotency key for the order: 1–64 letters, digits, `.`, `_`, `:` or `-`. Use a new key for each new order. Then place the order: ```bash curl -X POST https://api.coritan.com/api/v1/services/order \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "product_id": 31, "pricing_id": 87, "config": {"pool_id": 4, "prefix_len": 29}, "idempotency_key": "subnet-fra-2026-09-25" }' ``` `config.pool_id` is required. `config.prefix_len` takes the plan's size when you leave it out, and any other size is refused. With `"allow_pool_fallback": true` in `config`, we take the address from any other pool with stock when the chosen one has none left, and that pool can be in another region. To add a DDoS Shield profile, list it in `addons`, as [Add-ons in the order](/docs/get-started/order-a-service/#add-ons-in-the-order) describes. It protects the address this order buys. An order for a plan that costs $0 also needs a `turnstile_token` while the verification check is on, even with a paid profile in it. ```json { "service": {"id": 215, "status": "pending"}, "addons": [], "invoice_id": 5120, "requires_payment": true, "checkout_available": true, "amount_due": "32.00", "message": "Invoice created; pay to start provisioning" } ``` The response is shortened here. `service.id` becomes the floating IP's `service_id` once the address is assigned. When your credit balance covers the invoice, we pay it from there: `requires_payment` is `false` and `message` is `Payment applied from credit balance; provisioning started`. When `requires_payment` is `true`, pay invoice `invoice_id`, as [Pay an invoice](/docs/billing/invoices/) describes. Sending the same `idempotency_key` again returns the same order instead of a second one. Once the order has an invoice, the replay answers with `message` set to `Replayed existing IP order`. While the first request is still running, it answers `409` with `An identical IP order is already in progress; retry in a moment`. A key whose order was cancelled, failed or expired starts a new order. A second order for the same plan and billing cycle within two minutes of the first, while the first is still pending, returns the first order with `message` set to `Order already submitted`, whatever key it sends. Wait two minutes before you order the same plan again. Leaving out the key answers `422` with `idempotency_key is required for IP orders`, and a key in the wrong format answers `422` with a message that starts `idempotency_key must be`. Leaving out the pool answers `422` with `pool_id is required`. A `prefix_len` the plan does not sell answers `422` with a message that ends `Leave prefix_len out or set it to 29.` for a plan that sells `/29` subnets. A pool with nothing left answers `409` with `No available addresses in the selected pool`. `GET /api/v1/client/ips/orders` lists your open orders as `items`, newest first, up to 25. Each has its `idempotency_key`, `status`, `pool_id`, `prefix_len`, `version`, `service_id`, `invoice_id`, `hold_expires_at` and `last_error`. `status` is `started`, `held`, `invoiced` or `provisioning`, the four statuses the **Orders** tab shows. `GET /api/v1/client/ips/orders/{idempotency_key}` returns one order in any status. Besides the open statuses, `status` can be `active` once the address is assigned, or `cancelled`, `failed` or `expired`. An order with an invoice also carries `invoice_number`, `total`, `currency`, `due_date` and `invoice_status`. An unknown key answers `404` with `IP order not found`. `POST /api/v1/client/ips/orders/{idempotency_key}/cancel` cancels an open order and returns it: ```bash curl -X POST https://api.coritan.com/api/v1/client/ips/orders/subnet-fra-2026-09-25/cancel \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` An order that is `provisioning` or has ended answers `409` with `This IP order can no longer be cancelled`, and one whose address is already assigned answers `409` with `This IP is already provisioned`. The [Floating IPs API reference](/docs/api/reference/client/floating-ips/#op-get-api-v1-client-ips-orders) and the [services API reference](/docs/api/reference/client/services/#op-post-api-v1-services-order) list every field. ## API - `GET /api/v1/client/ips/orders`: Open platform IP order intents for resume after login or a new tab (https://www.coritan.com/docs/api/reference/client/floating-ips/#op-get-api-v1-client-ips-orders) - `GET /api/v1/client/ips/orders/{idempotency_key}`: Get my IP order (https://www.coritan.com/docs/api/reference/client/floating-ips/#op-get-api-v1-client-ips-orders-idempotency-key) - `POST /api/v1/client/ips/orders/{idempotency_key}/cancel`: Cancel my IP order (https://www.coritan.com/docs/api/reference/client/floating-ips/#op-post-api-v1-client-ips-orders-idempotency-key-cancel) - `POST /api/v1/services/order`: Order a platform service, and any add-ons bought with it (https://www.coritan.com/docs/api/reference/client/services/#op-post-api-v1-services-order) # IP pools and regions > The pools floating IPs come from, the region each one serves, and how to see what is in stock. Source: https://www.coritan.com/docs/floating-ips/pools/ In the dashboard: - /dashboard/networking/pools: https://www.coritan.com/dashboard/networking/pools A *pool* is a set of addresses we announce from one region. Every floating IP comes from a pool, and traffic to the address enters our network in that region. Choose a pool in the region where the instance or server you attach the address to runs. ## The Pools tab In the [dashboard](https://www.coritan.com/dashboard/networking), go to **Floating IPs** and select the **Pools** tab. It lists the pools that have addresses in stock, grouped by region. | What you see | What it means | | --- | --- | | Region heading | The region's name, its airport code such as `FRA`, and its country. | | Pool card | One pool, with how many sizes it has in stock. | | **Preferred** | The pool to choose first when a region has more than one. It is listed first. | | Size row | A size in stock, such as `IPv4 /32 one address` or `IPv4 /29 5 usable hosts`, with the plan that sells it. | | Price | The plan's price for its default billing period. `Price at checkout` means the plan's price did not load, and the order page shows it. | | **Order** | Opens the Floating IPs order page with this pool and size chosen, as [Order a floating IP](/docs/floating-ips/order-a-floating-ip/) describes. | | `Sold out` | The pool has no address of this size left. | | `Every pool is sold out` | No pool has stock. Addresses return as other customers release them. For a larger block, [contact support](/docs/support/conversations/). | Stock changes as customers order and release addresses. The tab and the order page show what is in stock when they load. ## Sizes | Prefix | What you get | | --- | --- | | `/32` | One address. | | `/29` to `/24` | A subnet. [How subnets work](/docs/floating-ips/subnets/) lists how many usable hosts each size has. | Every size is IPv4. Subnets appear only when the catalogue sells them, so a region can offer single addresses and no subnets. ## The pools API `GET /api/v1/client/ips/pools` lists the pools with stock of one size. | Query parameter | Default | Values | | --- | --- | --- | | `version` | `ipv4` | `ipv4` or `ipv6`. Floating IPs are IPv4, so leave it out. | | `prefix_len` | single addresses | `32` for single addresses, or `24` to `29` for subnets of that size. | | `region` | every region | An airport code in lower case, such as `fra`. | ```bash curl "https://api.coritan.com/api/v1/client/ips/pools?prefix_len=29" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "pools": [ { "id": 4, "uuid": "0b6c2f4e-6c4a-4d4e-9d61-1f1a2b3c4d5e", "name": "Frankfurt 1", "slug": "frankfurt-1", "region": "fra", "country_code": "DE", "geo_label": "Frankfurt", "preference_rank": 10, "available": true, "version": "ipv4", "prefix_len": 29 } ] } ``` | Field | Meaning | | --- | --- | | `id` | The pool's ID. Send it as `config.pool_id` when you order. | | `name`, `slug` | The pool's name as the dashboard shows it, and a short form of it. | | `region` | The region's airport code. | | `country_code` | The two-letter code of the pool's country. | | `geo_label` | The region's name. | | `preference_rank` | The order to prefer pools within a region, lowest first. | | `available` | `true` when the pool has stock of the size you asked for. | | `version`, `prefix_len` | The size you asked for. | Only pools with stock of the size appear, and the response gives no counts. | Response | When | | --- | --- | | `200` with `"pools": []` | No pool has stock of that size, or the catalogue does not sell subnets. | | `422` with `version must be ipv4 or ipv6` | `version` is another word. | | `422` with a message that starts `prefix_len must be one of` | `prefix_len` is not `32` or `24` to `29`. | The [Floating IPs API reference](/docs/api/reference/client/floating-ips/#op-get-api-v1-client-ips-pools) lists every field. ## API - `GET /api/v1/client/ips/pools`: Active geo pools with sellable stock for ordering (https://www.coritan.com/docs/api/reference/client/floating-ips/#op-get-api-v1-client-ips-pools) # Attach and detach a floating IP > Point a floating IP at an instance or a server, move it to another one, or take it off. Source: https://www.coritan.com/docs/floating-ips/attach-and-detach/ In the dashboard: - /dashboard/networking/ips/…/attach: https://www.coritan.com/dashboard/networking/ips Attach a floating IP to a Cloud Compute instance or a Container Apps server to make it answer on the address. Detach it to take the address off again. The floating IP stays on your account either way, so you can attach it somewhere else later. ## Before you begin - The floating IP must be active, which the list shows as `Attached` or `Unattached`. A suspended floating IP cannot be attached until you pay its invoice. - You need an instance or a server on your account to attach it to. - An instance sets up its addresses when it starts, so plan to restart it after you attach or detach an address. - A server holds one floating IP, and attaching or detaching can restart a running server. ## Attach a single address 1. In the [dashboard](https://www.coritan.com/dashboard/networking), go to **Floating IPs**, open the address and select the **Attach** tab. 2. Under **Choose a target**, select an instance or a server. Each one shows its name, status and type, and says `already has a floating IP` when it holds one. With more than six targets, a search box lets you find one by name. 3. For an instance, choose the address's role: - Tick **Make it the primary address** to make it the instance's main address. The current primary address becomes an additional one. - Leave it unticked to add the address next to the current one. An instance with no address takes the first one you attach as its primary address, ticked or not. 4. If you hold a coritan.gg name, you can tick **Also give the address a name** and type a name under it, such as `vm.survival-smp.coritan.gg`. We create an `A` record for the name in that zone once the address is attached. 5. Select **Attach address**. The message that confirms the attach also says what to do next. On an instance, restart the instance from its header. The instance sets up the address when it starts, as [Start, stop and restart an instance](/docs/cloud-compute/power/#restart-from-the-dashboard-to-apply-changes) explains, and a `reboot` typed inside the instance does not. An additional address gets a network interface of its own inside the instance, with no gateway. On a server, the ports the server publishes move onto the address, and [join addresses](/docs/proxies/join-addresses/) that point at the server follow it. For a Minecraft server, we also open the game's default ports on the address, `25565` for Java and `19132` for Bedrock, and make the matching one the server's main port, so players can connect with the address alone. A running server restarts only when the ports it uses change. The message says which applies: - `The server restarts to bind the new address.` - `The address answers right away; no restart needed.` - `Applies when the server next starts.` > [!NOTE] > A single address costs nothing while it is an instance's primary address, and bills at its plan's price anywhere else. Choosing the primary address therefore also chooses which address you pay for, as [What a floating IP costs](/docs/floating-ips/billing/#what-a-floating-ip-costs) explains. ## Attach a host of a subnet Each host of a subnet attaches to one instance or server. You can attach the other hosts of the same subnet to other targets, one at a time. 1. Open the subnet and select the **Attach** tab. You can also open the **Hosts** tab, open a free host's actions menu and select **Attach**, which opens the tab with that host chosen. 2. Under **Choose a target**, select the instance or server. 3. Under **Host address**, choose a host, or leave **First free host** to take the lowest free one. The hint under the field says how many hosts are free. 4. Select the button under the form. It reads **Attach address**, or `Attach` followed by the host when you chose one. Then restart the instance, or check the message for a server, as for a single address. ## Move a floating IP to another target A single address attaches to one target at a time, so you move it by detaching it and attaching it again. 1. On the **Attach** tab, select **Detach…** on the **Currently attached** card, then select **Detach address**. 2. Under **Choose a target**, select the new instance or server, and select **Attach address**. 3. Restart the instance you attached the address to. Also restart the instance you took it from, so that it stops using the address. The address keeps its reverse DNS and its DDoS Shield settings when it moves. ## Detach a floating IP 1. Open the floating IP and select **Detach…** in the header. On the **Attach** tab, the button is on the **Currently attached** card. 2. Read what the dialog says will stop working, then select **Detach address**. To detach one host of a subnet, open the **Hosts** tab, open the host's actions menu and select **Detach…**. On an instance, we take the address out of the instance's network settings. Restart the instance to apply the change. When the address was the primary one, the address that has been attached to the instance longest becomes the primary address. An instance with no address left has no public IPv4 address until you attach one. On a server, the ports the server published on the address go back to the address it used before, and the ports we opened on the floating IP close. Players then connect with that address and its port again. A running server restarts when the ports it uses change, and the message after detaching says so. The floating IP stays on your account, and we keep billing it until you [cancel it](/docs/floating-ips/billing/). ## Result The **Currently attached** card on the **Attach** tab shows the **Target**, its **Type** and **Since** when. On an instance, **Role** shows `Primary address` or `Additional address`. On a server, **Delivery** shows whether the address has reached the server's host. The list on the **IPs** tab shows the floating IP as `Attached`, and the address appears on the instance's **Network** tab. After a detach, the list shows `Unattached`, and the **Overview** tab shows **Not attached to anything yet**. ## Troubleshooting `Detach it first` under **Attach address** : The address is attached to another target. Detach it, then attach it to the new one, as [Move a floating IP to another target](#move-a-floating-ip-to-another-target) describes. A message that starts `IP is already attached to` : The address is attached elsewhere. The message names the service it is attached to. Detach it there first. `Container already has a primary floating IP; detach first` : The server already has a floating IP. Detach that one first, or choose another server. `Unsuspend the IP service before attaching` : The floating IP is suspended, usually for an unpaid invoice, and a suspension also detaches it. [Pay the open invoice](/docs/billing/invoices/), then attach it again. `IP service must be active or suspended to attach` : The floating IP is not ready yet. Wait until its order leaves the **Orders** tab. `No free host in this block` : Every usable host of the subnet is attached. Detach a host on the **Hosts** tab, or order another subnet. `Address is reserved (network/broadcast/gateway) and cannot be attached` : You chose the network, gateway or broadcast address of a subnet. Choose a usable host instead. [How subnets work](/docs/floating-ips/subnets/) explains which addresses those are. `Nothing to attach to` : You have no instance or server on your account. Select **Order service** to order one. A message that starts `This server's node` : The server's host cannot take a floating IP yet, and nothing was attached. [Contact support](/docs/support/conversations/) with the server's name. A message that starts `Failed to program guest IP` or `Could not place the floating IP on the server's host` : We could not set up the address on the target, and nothing was attached. Try again. If the message stays, [contact support](/docs/support/conversations/). `IP was attached by a concurrent request; refresh and try again` : Another tab attached the address at the same moment. Reload the page to see where it went. The address does not answer on an instance : Restart the instance from its header. Addresses are set up when the instance starts from the dashboard, and a `reboot` typed inside the instance does not set them up. **Last error** on the **Currently attached** card : The address has not reached the server's host. Detach it and attach it again. If the error comes back, [contact support](/docs/support/conversations/). ## Related - [Manage an instance's addresses and names](/docs/cloud-compute/networking/) - [Set reverse DNS for a floating IP](/docs/floating-ips/reverse-dns/) - [How subnets work](/docs/floating-ips/subnets/) - [Cancel a floating IP](/docs/floating-ips/billing/) - [How join addresses work](/docs/proxies/join-addresses/) ## With the API `GET /api/v1/client/ips/attach-targets` lists the instances and servers on your account that you can attach a floating IP to. ```bash curl https://api.coritan.com/api/v1/client/ips/attach-targets \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "items": [ {"service_id": 118, "hostname": "vm.example.com", "module_name": "vps", "resource_id": 57, "resource_status": "running", "has_floating_ip": true}, {"service_id": 131, "hostname": "survival-smp", "module_name": "container", "resource_id": null, "resource_status": "active", "has_floating_ip": false} ], "total": 2 } ``` `module_name` is `vps` for an instance and `container` for a server. `has_floating_ip` is `true` when the target already has a floating IP attached. `POST /api/v1/client/ips/{service_id}/attach` attaches the floating IP whose service ID is in the path: ```bash curl -X POST https://api.coritan.com/api/v1/client/ips/214/attach \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"target_type": "vps", "target_service_id": 118, "is_primary": false}' ``` | Field | Meaning | | --- | --- | | `target_type` | Required. `vps` for an instance, `container` for a server. | | `target_service_id` | Required. The target's `service_id` from the list above. | | `is_primary` | Instances only. `true` makes the address primary and `false` adds it next to the current one. Left out, the address becomes primary only when the instance has no address yet. | | `host_address` | Subnets only. The host to attach. Leave it out to attach the lowest free host. | | `host_inventory_id` | Subnets only. The host's `id` from the subnet's host list, in place of `host_address`. | The response is the new attachment: its `address`, `target_type`, `target_service_id`, `is_primary` and `attached_at`. Attaching an address to the target it is already on returns the existing attachment. For a server, the response also carries `publish`: - `requires_restart` is `true` when the server restarts to use the address. - `reachable_now` is `true` when the address answers without a restart. - `restart` is `not_needed`, `scheduled` or `pending`. `POST /api/v1/client/ips/{service_id}/detach` detaches the floating IP. The body is optional. For a subnet with more than one host attached, send the `host_address` or `host_inventory_id` of the host to detach: ```bash curl -X POST https://api.coritan.com/api/v1/client/ips/220/detach \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"host_address": "203.0.113.18"}' ``` The response has `detached` set to `true` and the removed attachment under `previous`, plus `publish` for a server. A floating IP that is not attached answers the same way with `already_detached` set to `true` and `previous` set to `null`. | Response | When | | --- | --- | | `404` | The floating IP, or the target service, is not on your account. | | `409` with a message that starts `IP is already attached to` | The address is attached to another target. | | `409` with `Container already has a primary floating IP; detach first` | The server already has a floating IP. | | `409` with `Unsuspend the IP service before attaching` | The floating IP is suspended. | | `409` with `No free hosts available in this subnet` | Every usable host of the subnet is attached. | | `422` with `target_service_id must be a Cloud Compute service` or `target_service_id must be a container service` | `target_type` does not match the service. | | `422` with `host_address / host_inventory_id only apply to subnet block services` | You sent a host for a single address. | | `422` with `Subnet has multiple attached hosts; pass host_address to detach one` | A detach of a subnet named no host. | The [Floating IPs API reference](/docs/api/reference/client/floating-ips/#op-post-api-v1-client-ips-service-id-attach) lists every field. ## API - `GET /api/v1/client/ips/attach-targets`: Thin list of owned compute services eligible as floating-IP attach targets (https://www.coritan.com/docs/api/reference/client/floating-ips/#op-get-api-v1-client-ips-attach-targets) - `POST /api/v1/client/ips/{service_id}/attach`: Attach IP (https://www.coritan.com/docs/api/reference/client/floating-ips/#op-post-api-v1-client-ips-service-id-attach) - `POST /api/v1/client/ips/{service_id}/detach`: Detach IP (or one subnet host) from its compute target; service remains on your account (https://www.coritan.com/docs/api/reference/client/floating-ips/#op-post-api-v1-client-ips-service-id-detach) # Set reverse DNS for a floating IP > Give a floating IP a PTR record so lookups of the address return your hostname. Source: https://www.coritan.com/docs/floating-ips/reverse-dns/ In the dashboard: - /dashboard/networking/ips/…/ptr: https://www.coritan.com/dashboard/networking/ips *Reverse DNS* answers the question "which hostname belongs to this address?". Set it for a floating IP so that a lookup of the address returns your hostname, such as `mail.example.com`. Mail servers check it before they accept mail, and tools such as `traceroute` show it. Each address has one reverse DNS record, stored as a `PTR` record. A subnet has one record for each usable host. ## Before you begin - The floating IP must be on your account. It does not need to be attached. - Choose a fully qualified hostname that you control, such as `mail.example.com`. If you have no domain, you can use a name under a free coritan.gg name, as [Claim a free coritan.gg name](/docs/proxies/coritan-gg-names/) describes. - For mail, the hostname also needs an `A` record that points at the address. [Make the name resolve both ways](#make-the-name-resolve-both-ways) explains how. - You can change reverse DNS as often as you like, but you cannot remove it once it is set. ## Set reverse DNS for a single address 1. In the [dashboard](https://www.coritan.com/dashboard/networking), go to **Floating IPs**, open the address and select the **Reverse DNS** tab. 2. Type the hostname in the field, such as `mail.example.com`. A trailing dot is optional. If you hold a coritan.gg name, the chips under the field offer names under it, such as `ip-203-0-113-10.survival-smp.coritan.gg`. Select a chip to fill the field. 3. Check the **Published record** preview. It shows the address's reverse name, such as `10.113.0.203.in-addr.arpa`, and the hostname it will return. 4. Select **Save reverse DNS**. A message confirms the change, such as `Reverse DNS for 203.0.113.10 set to mail.example.com.` ## Set reverse DNS for a host of a subnet 1. Open the subnet and select the **Reverse DNS** tab. You can also open the **Hosts** tab, open a host's actions menu and select **Set reverse DNS**, which opens the tab with that host chosen. 2. Under **Host**, choose the host. The list marks attached hosts with `(attached)`, and the hint under it says what the host is attached to. 3. Type the hostname and select **Save reverse DNS**. The list offers only the usable hosts. The network, gateway and broadcast addresses of a subnet have no reverse DNS of their own, as [How subnets work](/docs/floating-ips/subnets/) explains. ## Make the name resolve both ways The `PTR` record only answers lookups of the address. Mail servers also look up the hostname and expect it to return the same address. Create an `A` record for the hostname that points at the address: - For a domain on Coritan DNS, add the record on the domain's DNS records, as [Add, edit and delete DNS records](/docs/websites/dns/manage-dns-records/) describes. For a domain elsewhere, add it at your DNS provider. - For a name under your coritan.gg name, tick **Also give the address a name** on the **Attach** tab when you attach the address, as [Attach and detach a floating IP](/docs/floating-ips/attach-and-detach/) describes. For an address that is already attached, add the record to the name's zone as for any other domain. Then check both directions from any machine with `dig`: ```bash dig -x 203.0.113.10 +short dig mail.example.com A +short ``` The first command prints `mail.example.com.` and the second prints `203.0.113.10`. ## Result The **Reverse DNS** tab shows the saved hostname in the field and in the **Published record** preview. The **Overview** tab shows it under **Reverse DNS**. We publish the record in the reverse zone that covers the address, when we host that zone. Our nameservers then answer with the new hostname once you save it. Resolvers that looked the address up earlier can return the old answer until it expires. The record belongs to the address. It stays when you detach the address or move it to another instance or server. ## Troubleshooting `Enter a hostname` : The field is empty. Type a hostname. `Use a fully qualified name, for example mail.example.com` : The hostname has no dot. Type the full name, including the domain. `Empty label in hostname` : The hostname has two dots in a row, or starts with a dot. Remove the extra dot. `Each label is at most 63 characters` or `Hostnames are at most 253 characters` : A part of the hostname between dots, or the whole hostname, is too long. Choose a shorter name. A message that ends `is not a valid label` : A part of the hostname has a character other than a letter, a digit or a hyphen, or starts or ends with a hyphen. Remove the character. **Save reverse DNS** is greyed out : The hostname is the one already saved, or it breaks one of the rules above. `No usable hosts` : We could not list the subnet's hosts. [Contact support](/docs/support/conversations/) with the subnet's address. `dig -x` returns nothing or an old hostname : A resolver may still hold the old answer. Ask our nameserver directly with `dig -x 203.0.113.10 @ns1.coritan.com +short`. If it prints your hostname, wait for the resolver's copy to expire. If it prints nothing, or `dig` reports `REFUSED`, we do not host the reverse zone for the address, so the record is saved but not published. [Contact support](/docs/support/conversations/) with the address. Mail servers still reject your mail : The hostname's `A` record is missing or points at another address. Check both directions as [Make the name resolve both ways](#make-the-name-resolve-both-ways) shows. ## Related - [Attach and detach a floating IP](/docs/floating-ips/attach-and-detach/) - [How subnets work](/docs/floating-ips/subnets/) - [Add, edit and delete DNS records](/docs/websites/dns/manage-dns-records/) - [Claim a free coritan.gg name](/docs/proxies/coritan-gg-names/) ## With the API `GET /api/v1/client/ips/{service_id}/ptr` returns the reverse DNS record of a floating IP: ```bash curl https://api.coritan.com/api/v1/client/ips/214/ptr \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "id": 12, "inventory_id": 901, "hostname": "mail.example.com", "address": "203.0.113.10", "version": "ipv4", "service_id": 214, "created_at": "2026-09-25T10:00:00", "updated_at": "2026-09-25T10:00:00" } ``` An address with no reverse DNS returns `{"hostname": null, "ip_service_id": 214}`. For a subnet, name the host with the `host_address` query parameter, such as `?host_address=203.0.113.18`, or with `host_inventory_id`, the host's `id` from the subnet's host list. `PATCH /api/v1/client/ips/{service_id}/ptr` sets the record: ```bash curl -X PATCH https://api.coritan.com/api/v1/client/ips/214/ptr \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"hostname": "mail.example.com"}' ``` | Field | Meaning | | --- | --- | | `hostname` | Required. A fully qualified hostname of 1–253 characters. We store it in lower case, without a trailing dot. | | `host_address` | Subnets only. The host whose record you set. | | `host_inventory_id` | Subnets only. The host's `id` from the subnet's host list, in place of `host_address`. | The response is the saved record, as `GET` returns it. | Response | When | | --- | --- | | `422` with `PTR hostname must be a fully-qualified domain name` | The hostname has no dot. | | `422` with `PTR hostname must be a valid FQDN` | A part of the hostname breaks the rules in [Troubleshooting](#troubleshooting). | | `422` with `PTR hostname is required` | The hostname is blank. | | `422` with `Subnet PTR requires host_address or host_inventory_id` | The floating IP is a subnet and the request named no host. | | `422` with `Address is reserved (network/broadcast/gateway) and cannot be attached` | The host is one of the subnet's reserved addresses. | | `404` with a message that starts `Host in block` | The address is not part of the subnet. | | `403` with `You do not own this service` | The service is on another account. | | `409` with `Service is terminated or pending termination` | The floating IP has been cancelled. | The [Floating IPs API reference](/docs/api/reference/client/floating-ips/#op-patch-api-v1-client-ips-service-id-ptr) lists every field. ## API - `GET /api/v1/client/ips/{service_id}/ptr`: Get PTR (https://www.coritan.com/docs/api/reference/client/floating-ips/#op-get-api-v1-client-ips-service-id-ptr) - `PATCH /api/v1/client/ips/{service_id}/ptr`: Set PTR (https://www.coritan.com/docs/api/reference/client/floating-ips/#op-patch-api-v1-client-ips-service-id-ptr) # How subnets work > What a floating subnet holds, which of its addresses you can use, and how to list them. Source: https://www.coritan.com/docs/floating-ips/subnets/ In the dashboard: - /dashboard/networking/ips/…/hosts: https://www.coritan.com/dashboard/networking/ips A *subnet* is a block of consecutive IPv4 addresses that you hold as one floating IP. You order it in one size, pay one price for it, and attach its addresses one at a time. Each address that you can attach is a *host*. A subnet is written with its size, such as `203.0.113.16/29`: the block starts at `203.0.113.16`, and `/29` says how many addresses it holds. ## Sizes | Size | Addresses | Usable hosts | | --- | --- | --- | | `/29` | 8 | 5 | | `/28` | 16 | 13 | | `/27` | 32 | 29 | | `/26` | 64 | 61 | | `/25` | 128 | 125 | | `/24` | 256 | 253 | The **Pools** tab shows which sizes you can order, and in which regions, as [IP pools and regions](/docs/floating-ips/pools/) describes. When it lists no subnet sizes, you can order single addresses only. ## Addresses the network keeps Three addresses of every subnet are reserved, and you cannot attach them. In `203.0.113.16/29`: *Network address* : The first address, `203.0.113.16`. *Gateway* : Usually the address after it, `203.0.113.17`. The subnet's **Overview** tab shows it under **Gateway**. *Broadcast address* : The last address, `203.0.113.23`. The addresses between them, `203.0.113.18` to `203.0.113.22`, are the usable hosts. ## How hosts reach your machines Each host attaches on its own, to one Cloud Compute instance or one Container Apps server. You can attach different hosts of one subnet to different targets, and several hosts to one instance. A server holds one floating IP, so it takes one host. [Attach and detach a floating IP](/docs/floating-ips/attach-and-detach/#attach-a-host-of-a-subnet) has the steps. We route each attached host to its target as a single address. An instance sets the host up with the prefix `/32`, the same way as a single floating IP, and does not see the rest of the subnet. A host that is not attached reaches none of your machines. ## The Hosts tab Open the subnet and select the **Hosts** tab. The card is titled with the subnet, such as `203.0.113.16/29`, and says how many usable hosts are attached and which address is the gateway, such as `2 of 5 usable hosts attached · gateway 203.0.113.17`. The table lists every address of the subnet: **Address** : The address. Select it to copy it. **Role** : `Gateway`, `Network` or `Broadcast` for a reserved address. `Attached` or `Free` for a usable host. **Attached to** : The instance or server the host is attached to. A reserved address shows `Reserved by the network`. **Since** : When the host was attached. The actions menu at the end of a usable host's row holds **Set reverse DNS** and, for a free host, **Attach**, or for an attached host, **Detach…**. ## Reverse DNS and DDoS Shield on a subnet Each usable host has a reverse DNS record of its own. [Set reverse DNS for a floating IP](/docs/floating-ips/reverse-dns/#set-reverse-dns-for-a-host-of-a-subnet) explains how to set one. DDoS Shield filters the whole subnet with one profile. To aim a rule at a single host, put the host in the rule's **Destination prefix**, such as `203.0.113.18/32`, as [Change a floating IP's DDoS protection](/docs/floating-ips/shield-settings/) describes. ## Billing and suspension A subnet always bills at its plan's price, whether or not any host is attached. A host that is an instance's primary address does not make the subnet free. [What a floating IP costs](/docs/floating-ips/billing/#what-a-floating-ip-costs) compares this with a single address. When we suspend a subnet, usually for an unpaid invoice, we detach every host. After you pay, attach the hosts again. When a subnet ends, we detach every host and release the whole block. You cannot change a subnet's size. For more addresses, order another subnet. ## With the API `GET /api/v1/client/ips/{service_id}/hosts` lists every address of a subnet with what it is attached to: ```bash curl https://api.coritan.com/api/v1/client/ips/220/hosts \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "service_id": 220, "kind": "block", "cidr": "203.0.113.16/29", "gateway": "203.0.113.17", "hosts": [ {"id": 931, "address": "203.0.113.16", "role": "reserved", "attached": false, "attachable": false, "attached_to": null, "attached_at": null}, {"id": 933, "address": "203.0.113.18", "role": "host", "attached": true, "attachable": false, "attached_to": {"target_type": "vps", "target_service_id": 118, "target_resource_id": 57}, "attached_at": "2026-09-25T10:00:00"}, {"id": 934, "address": "203.0.113.19", "role": "host", "attached": false, "attachable": true, "attached_to": null, "attached_at": null} ], "hosts_total": 5, "hosts_attached": 1, "hosts_available": 4 } ``` The example shortens `hosts` to three entries. The list holds every address of the subnet in address order. | Field | Meaning | | --- | --- | | `hosts[].id` | The host's ID. The attach, detach and reverse DNS requests take it as `host_inventory_id`. | | `hosts[].role` | `host` for a usable host, `reserved` for the network, gateway and broadcast addresses. | | `hosts[].attachable` | `true` for a usable host that is free. | | `hosts[].attached_to` | The target's `target_type` (`vps` or `container`) and `target_service_id`, or `null`. | | `hosts_total` | The number of usable hosts. The reserved addresses do not count. | | `hosts_attached`, `hosts_available` | How many usable hosts are attached, and how many are free. | Add `?include_reserved=false` to leave the reserved addresses out of `hosts`. A floating IP that is a single address answers `400` with `Service is not a subnet block`. A service that is not a floating IP on your account answers `404` with `IP service not found`. The [Floating IPs API reference](/docs/api/reference/client/floating-ips/#op-get-api-v1-client-ips-service-id-hosts) lists every field. ## API - `GET /api/v1/client/ips/{service_id}/hosts`: Host map for a rented subnet service (https://www.coritan.com/docs/api/reference/client/floating-ips/#op-get-api-v1-client-ips-service-id-hosts) # Change a floating IP's DDoS protection > Choose how DDoS Shield filters traffic to a floating IP, set its rate limits and add rules for ports and sources. Source: https://www.coritan.com/docs/floating-ips/shield-settings/ In the dashboard: - /dashboard/networking/ips/…/shield: https://www.coritan.com/dashboard/networking/ips DDoS Shield filters the traffic to every floating IP on your account, attached or not. Each floating IP starts on the *platform default profile*, the settings we apply to every address. On the **Shield** tab you can give an address its own profile, then choose how strictly we filter it, set its rate limits and add rules that allow, drop or rate limit traffic by protocol, source and port. [How DDoS Shield works](/docs/ddos-shield/how-ddos-shield-works/) explains the filtering itself. ## Before you begin - The floating IP must be on your account and not cancelled. - Know the protocols and ports your services use, such as TCP `443` for a website, TCP `22` for SSH or TCP `25565` for a Minecraft Java server. A rule that drops them cuts your service off. - Once you customise an address, it keeps a profile of its own, and this tab has no control that returns it to the platform default. Binding the address to a [custom profile](/docs/ddos-shield/custom-profiles/) and then unbinding it does return it to the platform default. - A subnet has one profile for all of its hosts. ## See how the address is protected 1. In the [dashboard](https://www.coritan.com/dashboard/networking), go to **Floating IPs**, open the address and select the **Shield** tab. 2. Read the profile card and the **Rules** card under it. An address on the default shows the **Platform default profile** card, which lists its **Protection mode**, what happens **When no rule matches**, how it treats **Established connections**, and its rate limits. You cannot change the default itself. An address with its own profile shows the profile's name, such as `Customer IP 214`, with the badge `Custom`, and a form in place of the list. ## Give the address its own profile 1. On the **Platform default profile** card, select **Customise**. A message confirms `This address now has its own protection profile.` Adding a rule, as [Add a rule](#add-a-rule) describes, does the same. The new profile starts with: - **Protection mode** set to **Custom** - **When no rule matches** set to **Allow** - **Pass established connections** ticked - the rate limits in [Rate limits](#rate-limits) - no rules ## Change the profile 1. On the profile card, change any of the fields that the sections below describe. 2. Select **Save profile**. A message confirms `Protection profile saved.`, and the card footer shows when the profile last changed. As the card says, the change reaches our filtering within a minute. ### Protection modes **Standard** : We filter the address with the platform's own checks and rate limits. Your rules and **When no rule matches** still apply. The rate limits on your profile do not. **Custom** : The same as **Standard**, with the rate limits on your profile in place of the platform's. **Passthrough** : We skip our checks, your rate limits and your rules. We still drop packets that are malformed or could not be real, such as a packet that claims to come from the address it is sent to. > [!WARNING] > With **Passthrough**, an attack on the address reaches your instance or server in full. Use it only for as long as you need to. In **Standard** and **Custom**, only TCP, UDP and ICMP reach the address. We drop every other protocol, such as GRE or ESP. We also drop UDP packets sent from the ports that reflection attacks use, such as `53` for DNS and `123` for NTP. Your rules cannot let these packets through. [How DDoS Shield works](/docs/ddos-shield/how-ddos-shield-works/#reflection-ports) lists the ports. ### When no rule matches **Allow** : Traffic gets through unless a rule drops it. This is the setting a new profile starts with. **Drop** : Only traffic that an **Allow** rule matches gets through. **Pass established connections** matters only when **When no rule matches** is **Drop**. It lets through a TCP packet that belongs to a connection already open and is sent to port 1024 or above, with no **Allow** rule needed. This keeps replies to the connections your machine opens working. A rule that matches the packet still decides, and the rate limits still apply. > [!IMPORTANT] > Before you choose **Drop**, add an **Allow** rule for every port your services use. **Pass established connections** covers TCP only, so also add **Allow** rules for the UDP and ICMP replies your machine expects, such as an **ICMP** rule for ping. ### Rate limits The rate limits apply only when **Protection mode** is **Custom**. Traffic over a limit is dropped. | Field | Range | A new profile starts at | What it limits | | --- | --- | --- | --- | | **Packets per second per source** | 100–10,000,000 | 100,000 | Packets from one sender. | | **Aggregate packets per second** | 10,000–100,000,000 | 10,000,000 | All traffic to the address together. | | **SYN per second per source** | 100–10,000,000 | 10,000 | New TCP connections from one sender. | | **ICMP per second per source** | 10–1,000,000 | 5,000 | Pings and other ICMP from one sender. | ## Add a rule 1. On the **Rules** card, select **Add rule…**. 2. Fill in the **Add rule** form. Leave a field empty to match anything. - **Name**: a name to know the rule by, such as `ssh-office`. - **Action**: **Drop**, **Allow** or **Rate limit**. For **Rate limit**, also enter the **Limit (packets per second)** that each sender may send. - **Protocol**: **Any protocol**, **TCP**, **UDP** or **ICMP**. - **Source prefix**: the senders the rule applies to, such as `198.51.100.0/24`. - **Destination prefix**: leave it empty to match the whole floating IP. On a subnet, enter a host, such as `203.0.113.18/32`, to aim the rule at that host. - **Source ports** and **Destination ports**: for one port, fill in the first box. For a range, fill in both, such as `27015` and `27020`. - **Notes**: why the rule exists. 3. Select **Add rule**. > [!IMPORTANT] > Write every prefix with its length, such as `198.51.100.7/32` for a single sender. A prefix without a length, or a port in the second box alone, does not limit the rule, so it matches every sender or every port. A message confirms the rule, such as `Rule "ssh-office" added.` You cannot edit a rule. To change one, add the new version and delete the old one. ## How rules are applied Traffic to the address passes our packet checks and the rate limits first. Your rules then decide what happens to the rest: - When one rule matches a packet, its action applies. - When several rules match, the most specific rule decides. A rule is more specific when it names more about the packet: a protocol, a narrower prefix, or a single port in place of a range. - When two matching rules are equally specific, **Drop** wins. - When no rule matches, **When no rule matches** decides. The order in which you add rules does not matter. A rule that matches everything, which the table shows as `Every packet`, only takes effect as **Drop**, so give an **Allow** or **Rate limit** rule at least one field. A **Rate limit** rule counts each sender on its own. It lets a sender through at up to the limit, with short bursts of up to twice the limit, and drops the rest. These rule sets cover common needs: | Goal | Rules | | --- | --- | | Allow SSH from your office only | **Drop**, **TCP**, destination port `22`. Then **Allow**, **TCP**, source prefix `198.51.100.0/24`, destination port `22`. The second rule is more specific, so it wins for your office. | | Block one sender | **Drop**, **Any protocol**, source prefix `198.51.100.7/32`. | | Limit each sender on a Bedrock server | **Rate limit**, **UDP**, destination port `19132`, with a limit such as `2000`. | ## Delete a rule 1. On the **Rules** card, select **Delete…** on the rule's row. 2. Type the rule's name and select **Delete rule**. Traffic that the rule matched then meets your other rules and **When no rule matches**. ## Result The profile card shows your profile's settings. Its badge reads `Custom`, followed by `· Standard` or `· Passthrough` when you chose one of those modes. The **Rules** table lists each rule with its **Action**, what it **Matches**, such as `TCP · from 198.51.100.0/24 · to port 22`, and its **State**. Attacks on the address show on the **Events** tab of [DDoS Shield](https://www.coritan.com/dashboard/ddos/events), as [Read attack events](/docs/ddos-shield/attack-events/) describes. ## Troubleshooting Your service stopped answering after a change : A rule, or **When no rule matches** set to **Drop**, drops traffic the service needs. Add an **Allow** rule for its protocol and port, or set **When no rule matches** back to **Allow**. A rule drops far more than you meant : The rule has a prefix without a length, or a port in the second box alone, so it matches everything. Delete it and add it again with the full prefix, such as `198.51.100.7/32`, and the port in the first box. An **Allow** rule has no effect : The rule has no fields, or a more specific **Drop** rule matches the same traffic. Give the rule a protocol, prefix or port that is at least as specific as the **Drop** rule. Your rate limits have no effect : **Protection mode** is **Standard**, which uses the platform's own limits. Set it to **Custom**. A GRE or IPsec tunnel to the address does not connect : In **Standard** and **Custom** we drop GRE and ESP, and UDP from ports `500` and `4500`, which IPsec uses. Use a tunnel that runs over UDP on another port, such as WireGuard. DNS lookups or time sync on the machine time out : DNS and NTP servers answer from UDP ports `53` and `123`. In **Standard** and **Custom** we drop those packets when they are sent to this address, and a rule cannot let them through. [Contact support](/docs/support/conversations/) if a service on the address needs one of these ports. There is no **Delete…** button on the rules : The address is on the platform default profile, and its rules belong to the platform. Select **Customise** to give the address a profile of its own. `Could not save the protection profile` or `Could not add the rule` : We refused the change. The message beside it gives the reason. Correct the field it names and try again. ## Related - [How DDoS Shield works](/docs/ddos-shield/how-ddos-shield-works/) - [Read attack events](/docs/ddos-shield/attack-events/) - [How subnets work](/docs/floating-ips/subnets/) - [Attach and detach a floating IP](/docs/floating-ips/attach-and-detach/) ## With the API The requests take the floating IP's service ID in the path. `GET /api/v1/client/shield/subjects/{ip_service_id}/profile` returns the profile that filters the address, with its rules: ```bash curl https://api.coritan.com/api/v1/client/shield/subjects/214/profile \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "mode": "custom", "editable": true, "profile": { "id": 57, "name": "Customer IP 214", "protection_mode": "custom", "default_action": "allow", "established_passthrough": true, "rate_limits": {"per_source_pps": 100000, "aggregate_pps": 10000000, "syn_pps_per_source": 10000, "icmp_pps_per_source": 5000}, "updated_at": "2026-09-25T10:00:00" }, "rules": [ {"id": 311, "name": "ssh-office", "enabled": 1, "action": "allow", "protocol": 6, "src_prefix": "198.51.100.0/24", "dst_prefix": null, "dst_port_min": 22, "dst_port_max": 22, "rate_limit_pps": null} ] } ``` The example leaves out fields the dashboard does not show. Until the address has its own profile, `mode` is `platform_default`, `editable` is `false`, and `profile` and `rules` describe the platform default profile. `PATCH /api/v1/client/shield/subjects/{ip_service_id}/profile` gives the address its own profile, when it has none, and changes it: ```bash curl -X PATCH https://api.coritan.com/api/v1/client/shield/subjects/214/profile \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"default_action": "deny", "rate_limits": {"per_source_pps": 50000}}' ``` | Field | Meaning | | --- | --- | | `protection_mode` | `standard`, `custom` or `passthrough`. | | `default_action` | `allow`, or `deny` for **Drop**. | | `established_passthrough` | `true` or `false`. | | `rate_limits` | Any of `per_source_pps`, `aggregate_pps`, `syn_pps_per_source` and `icmp_pps_per_source`, within the ranges in [Rate limits](#rate-limits). A limit you leave out keeps its value. | Every field is optional. Send `{}` to give the address its own profile and change nothing. The response has the same shape as `GET`. A `protection_mode` or `default_action` outside its words answers `422` with `protection_mode must be standard|custom|passthrough` or `default_action must be allow|deny`. A floating IP that is not on your account, or is cancelled, answers `404` with a message that starts `IP service`. `POST /api/v1/client/shield/subjects/{ip_service_id}/rules` adds a rule, and gives the address its own profile first when it has none: ```bash curl -X POST https://api.coritan.com/api/v1/client/shield/subjects/214/rules \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "ssh-office", "action": "allow", "protocol": 6, "src_prefix": "198.51.100.0/24", "dst_port_min": 22, "dst_port_max": 22}' ``` | Field | Meaning | | --- | --- | | `name` | 1–191 characters. | | `action` | `deny` (the default), `allow` or `rate_limit`. | | `protocol` | The IP protocol number: `6` for TCP, `17` for UDP, `1` for ICMP. Leave it out to match any protocol. | | `src_prefix`, `dst_prefix` | A prefix with its length, such as `198.51.100.0/24`. | | `src_port_min`, `src_port_max`, `dst_port_min`, `dst_port_max` | A port range. For one port, send the same number as both the minimum and the maximum. | | `rate_limit_pps` | For `rate_limit`, the packets per second each sender may send. | | `enabled` | `false` saves the rule without applying it, and the dashboard shows it as `Off`. It defaults to `true`. | | `notes` | Free text. | The API also takes matches that the dashboard does not offer: `tcp_flags_mask` with `tcp_flags_value`, `packet_len_min` and `packet_len_max`, `ttl_min` and `ttl_max`, and `icmp_type` with `icmp_code`. The response is the new rule with its `id`. An `action` outside its words answers `422` with `action must be allow|deny|rate_limit`. The API does not check the other fields, so check them yourself. A prefix without a length matches every address, and a `*_port_max` without its `*_port_min` matches every port. A `rate_limit` rule without `rate_limit_pps` allows 1,000 packets per second from each sender. `DELETE /api/v1/client/shield/subjects/{ip_service_id}/rules/{rule_id}` deletes a rule and answers `{"status": "deleted"}`: ```bash curl -X DELETE https://api.coritan.com/api/v1/client/shield/subjects/214/rules/311 \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` A rule of another profile answers `403` with `Rule does not belong to this subject profile`. An address without a profile of its own answers `404` with a message that starts `Custom Shield profile for IP`. The [DDoS Shield API reference](/docs/api/reference/client/shield/#op-patch-api-v1-client-shield-subjects-ip-service-id-profile) lists every field. ## API - `GET /api/v1/client/shield/subjects/{ip_service_id}/profile`: The profile scrubbing this IP: the platform default, or its custom one (https://www.coritan.com/docs/api/reference/client/shield/#op-get-api-v1-client-shield-subjects-ip-service-id-profile) - `PATCH /api/v1/client/shield/subjects/{ip_service_id}/profile`: Ensure custom profile (https://www.coritan.com/docs/api/reference/client/shield/#op-patch-api-v1-client-shield-subjects-ip-service-id-profile) - `POST /api/v1/client/shield/subjects/{ip_service_id}/rules`: Create subject rule (https://www.coritan.com/docs/api/reference/client/shield/#op-post-api-v1-client-shield-subjects-ip-service-id-rules) - `DELETE /api/v1/client/shield/subjects/{ip_service_id}/rules/{rule_id}`: Delete subject rule (https://www.coritan.com/docs/api/reference/client/shield/#op-delete-api-v1-client-shield-subjects-ip-service-id-rules-rule-id) # Cancel a floating IP > See what a floating IP costs, and cancel it at the end of its term or straight away. Source: https://www.coritan.com/docs/floating-ips/billing/ In the dashboard: - /dashboard/networking/ips/…/billing: https://www.coritan.com/dashboard/networking/ips Each floating IP is a service of its own, with its own price and renewal date. Its **Billing** tab shows what it costs. Cancel it when you no longer need the address, and we release the address back to our pool. [Cancel a service](/docs/billing/cancel-a-service/) explains how cancelling works for every product. This page covers what it does to a floating IP. ## Before you begin - Find everything that uses the address itself: DNS records that point at it, players or clients that connect to it, and allow lists at other providers. Once the address is released, it no longer reaches you. [Join addresses](/docs/proxies/join-addresses/) of a server follow the server back to its own address. - If the floating IP is the address that came with an instance, cancelling it takes the instance's public IPv4 address away. ## What a floating IP costs - A single address costs nothing while it is the primary address of a Cloud Compute instance. - Anywhere else, a single address bills at its plan's price: while it is unattached, as an additional address on an instance, or on a Container Apps server. - A subnet always bills at its plan's price. The price follows the address. When you attach or detach it, or make another address an instance's primary address, we update the price. The **Price** on the **Billing** and **Overview** tabs is what the next renewal costs. We create each renewal invoice ahead of the **Next renewal** date, at the price that applies then. The IPv4 address that comes with an instance is its primary address, so it costs nothing. When you make another floating IP the instance's primary address, that one becomes free and the address that came with the instance bills at its plan's price. The order page shows each plan's price before you order, as [Order a floating IP](/docs/floating-ips/order-a-floating-ip/) describes. ## See the plan 1. In the [dashboard](https://www.coritan.com/dashboard/networking), go to **Floating IPs**, open the address and select the **Billing** tab. 2. Read the **Current plan** card. **Product** and **Plan** : The floating IP plan and billing period you ordered. **Price** : What the next renewal costs, as [What a floating IP costs](#what-a-floating-ip-costs) explains. **Status** : The state of the service, such as `Active` or `Suspended`. **Next renewal** : The date of the next renewal. A floating IP billed by the hour shows **Used this month** instead. **Set up** and **Service ID** : When we assigned the address, and the number to quote when you contact support. The **Activity** table under the card lists setup, renewals and other changes to the service. You cannot change a floating IP's plan, so the tab has no **Change plan…** button. For another size or billing period, order a new floating IP, move your targets to it, then cancel the old one. ## Cancel the floating IP 1. On the **Billing** tab, select **Cancel service…** on the **Cancel service** card. 2. Under **When**, choose when the floating IP ends: - **At the end of the current term** keeps it until the paid period ends. The dialog shows the date. - **Immediately** ends it now. We do not refund the unused time. A floating IP billed by the hour has no choice. It ends at once, and billing stops at the hour. 3. Type `cancel` and select **Cancel service**. > [!WARNING] > When the floating IP ends, we detach it from its instance or server and release the address to our pool. You cannot get the address back, and another customer can order it. Its DDoS Shield profile stops applying. We do not remove its reverse DNS. A subnet ends as a whole: we detach every host and release the whole block. ## Result After you cancel at the end of the term, the message reads `Scheduled for end-of-term termination; service remains active until then`. The **Billing** tab shows **Cancellation scheduled** with the date the floating IP ends. The address keeps working until then, and we raise no more renewal invoices for it. After an immediate cancellation, the message reads `Immediate termination queued`, and we detach and release the address. Either way, we cancel the floating IP's unpaid invoices, and we email you about the cancellation. ## Troubleshooting `Service already scheduled for end-of-term termination` : You already cancelled the floating IP at the end of its term. To end it sooner, [contact support](/docs/support/conversations/). `Service already cancelled/terminated` : The floating IP has ended or is being released. You want to keep a floating IP that is scheduled to end : [Contact support](/docs/support/conversations/) before the date that **Cancellation scheduled** shows. `Suspended` : We suspended the floating IP, usually for an unpaid invoice, and a suspension also detaches it. [Pay the open invoice](/docs/billing/invoices/), then [attach it again](/docs/floating-ips/attach-and-detach/). The **Price** went up : The address is no longer an instance's primary address. It may have been detached, moved to a server, or replaced as primary by another address. [What a floating IP costs](#what-a-floating-ip-costs) lists when a single address is free. ## Related - [Cancel a service](/docs/billing/cancel-a-service/) - [Pay an invoice](/docs/billing/invoices/) - [How hourly billing works](/docs/billing/hourly-billing/) - [Attach and detach a floating IP](/docs/floating-ips/attach-and-detach/) - [Order a floating IP](/docs/floating-ips/order-a-floating-ip/) ## With the API Cancel a floating IP through the services API, with its service ID as the service reference: ```bash curl -X POST https://api.coritan.com/api/v1/services/214/cancel \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"immediate": false, "reason": "Moved to a subnet"}' ``` Send `immediate` as `true` to end the floating IP now, or `false` (the default) to end it at the end of its term. `reason` is optional. `keep_snapshot` has no effect on a floating IP. ```json { "message": "Scheduled for end-of-term termination; service remains active until then", "immediate": false, "status": "active", "termination_date": "2026-10-25T00:00:00", "snapshot_taken": false } ``` `GET /api/v1/client/ips/{service_id}` returns the floating IP's current price in `amount` and its renewal date in `next_due_date`, as [Floating IPs](/docs/floating-ips/#with-the-api) describes. The [services API reference](/docs/api/reference/client/services/#op-post-api-v1-services-service-ref-cancel) lists every field. # Troubleshoot floating IPs > Fixes for orders that stay pending, addresses that will not attach, and reverse DNS that does not resolve. Source: https://www.coritan.com/docs/floating-ips/troubleshooting/ ## The order stays on the Orders tab Cause : The order waits for its invoice, the hold on its address has expired, or we could not assign the address. The **Status** column says which, and an error shows under it when the last attempt failed. Fix : For `Awaiting payment`, select **Open invoice** and pay it. We cancel an order whose invoice is still unpaid after two days, by default. For `Hold expired`, cancel the order and place a new one. If an error stays under a paid order, [contact support](/docs/support/conversations/). [Follow the order](/docs/floating-ips/order-a-floating-ip/#follow-the-order) explains each status. ## The floating IP shows as Pending Cause : The floating IP's service is not active. Its first invoice is unpaid, or we suspended it. Fix : Pay the open invoice from the **Orders** tab or the floating IP's **Billing** tab. A suspension also detaches the floating IP, so [attach it again](/docs/floating-ips/attach-and-detach/) after you pay. ## The floating IP will not attach Cause : The address is attached to another target, the server already has a floating IP, the floating IP is suspended, or every host of the subnet is in use. The message under the form names the cause. Fix : Follow the entry for that message in [Attach and detach a floating IP](/docs/floating-ips/attach-and-detach/#troubleshooting). ## The address does not answer after you attach it Cause : An instance sets up its addresses when it starts, so a new address does nothing until the instance restarts from the dashboard. On a server, the message after attaching may say the address applies when the server next starts. A firewall inside the machine, or a DDoS Shield rule, can also drop the traffic. Fix : Restart the instance from its header, as [Start, stop and restart an instance](/docs/cloud-compute/power/#restart-from-the-dashboard-to-apply-changes) describes, or restart the server. Check that the firewall inside the machine allows the port. Then check the address's **Shield** tab, as [the next entry](#ddos-shield-drops-traffic-you-want) describes. ## DDoS Shield drops traffic you want Cause : A rule matches the traffic, or **When no rule matches** is **Drop** and no **Allow** rule names it. A rule with a prefix that has no length, or with a port in the second box alone, matches everything. In the **Standard** and **Custom** modes, only TCP, UDP and ICMP reach the address. Fix : Open the address's **Shield** tab and correct the rules, as [Change a floating IP's DDoS protection](/docs/floating-ips/shield-settings/#troubleshooting) describes. The **Events** tab of [DDoS Shield](https://www.coritan.com/dashboard/ddos/events) shows attacks on your addresses. ## Reverse DNS does not resolve Cause : Resolvers still hold the old answer, or we do not host the reverse zone that covers the address. A hostname whose `A` record points elsewhere also fails the check mail servers make. Fix : Ask our nameserver directly with `dig -x 203.0.113.10 @ns1.coritan.com +short`, and follow [Set reverse DNS for a floating IP](/docs/floating-ips/reverse-dns/#troubleshooting) for what the answer means. ## The floating IP was detached without you Cause : We suspended it, usually for an unpaid invoice, which detaches it. An instance or server that ends also leaves the floating IPs attached to it detached, and they stay on your account. Fix : Pay the open invoice if there is one, then [attach the floating IP again](/docs/floating-ips/attach-and-detach/). ## The address that came with an instance is gone Cause : The IPv4 address that comes with a Cloud Compute instance ends with the instance, even when you attached it somewhere else. Fix : You cannot get the address back. For an address that must outlast an instance, [order a floating IP](/docs/floating-ips/order-a-floating-ip/) on its own and attach it to the instance. ## The price of a floating IP went up Cause : A single address costs nothing only while it is an instance's primary address. It was detached, moved to a server, or replaced as primary by another address. Fix : Make it the primary address of an instance again, or cancel it if you no longer need it. [What a floating IP costs](/docs/floating-ips/billing/#what-a-floating-ip-costs) has the rules. ## None of these match [Contact support](/docs/support/conversations/) with the floating IP's address and its **Service ID**, which the **Overview** and **Billing** tabs show. For problems inside an instance, see [Troubleshoot instances](/docs/cloud-compute/troubleshooting/). # DDoS Shield > DDoS Shield filters attack traffic before it reaches your floating IPs, and shows what it blocked. Source: https://www.coritan.com/docs/ddos-shield/ In the dashboard: - /security/ddos: https://www.coritan.com/security/ddos - /dashboard/ddos: https://www.coritan.com/dashboard/ddos - /dashboard/ddos/overview: https://www.coritan.com/dashboard/ddos/overview DDoS Shield filters the traffic sent to your floating IPs at Coritan's points of presence, so an attack is dropped before it reaches your instance or server. Every floating IP on your account is protected from the moment we assign it, whether or not it is attached. You do not have to turn anything on. The platform default profile and a profile of an address's own cost nothing beyond the floating IP, and a custom profile is a paid service with plans of its own. ## What every address gets Each floating IP starts on the *platform default profile*. It drops packets that attacks use and normal traffic does not, such as IP fragments, packets from private addresses and UDP answers from the ports that reflection attacks use. It also limits how fast one sender, and all senders together, may send to the address. Only TCP, UDP and ICMP reach the address. [How DDoS Shield works](/docs/ddos-shield/how-ddos-shield-works/) lists every check. DDoS Shield does not filter the shared address of a Container Apps server. To protect a server, attach a floating IP to it ([How the Coritan network works](/docs/platform/network/#traffic-to-a-shared-address)). ## What you can add - *A profile of the address's own*, at no charge. On the address's **Shield** tab, choose how strictly we filter it, set its rate limits and add rules that allow, drop or rate limit traffic by protocol, source and port ([Change a floating IP's DDoS protection](/docs/floating-ips/shield-settings/)). - *A custom profile*, as a paid service. It holds one set of limits and rules for every address you bind to it, and lets you block traffic sources ([Order and use a custom profile](/docs/ddos-shield/custom-profiles/)). ## Read the Overview tab In the [dashboard](https://www.coritan.com/dashboard/ddos), select **DDoS Shield** in the sidebar. The page has three tabs, **Overview**, **Events** and **Profiles**, and two buttons: **Refresh** reloads the page's data, and **Order custom profile** opens the order page for a custom profile ([Order a custom profile](/docs/ddos-shield/custom-profiles/#order-a-custom-profile)). The **Overview** tab opens first. When the account has no floating IP yet, it shows **Nothing to protect yet** and **Order floating IP** instead ([Order a floating IP](/docs/floating-ips/order-a-floating-ip/)). Otherwise it starts with four figures: **Protected addresses** : How many floating IPs DDoS Shield filters, with how many are attached and how many are not. Both kinds are filtered. **Attacks · 24h** : How many attack events started in the last 24 hours. The line under it says how many DDoS Shield is still mitigating, when the last one started, or `Nothing seen yet`. **Mitigated traffic · 24h** : An estimate of the traffic we dropped in those events, worked out from each event's peak rate and duration. The line under it names the attack type with the highest peak. **Current mode** : `Platform default` when every address is on the platform default profile. Otherwise it reads `Custom on` with the number of addresses that have a profile of their own or a custom profile, such as `Custom on 2`. When an attack is in progress, a warning lists each one with the address it targets and when it started. **Open Events** opens the **Events** tab ([Read attack events](/docs/ddos-shield/attack-events/)). The **Addresses** card lists every floating IP on the account: - **Address**: the address, which links to the floating IP's page. - **Attached to**: the instance or server it is attached to, `Not attached`, or `Per host` for a subnet. - **Protection**: `Platform default`, or `Custom` for an address with its own profile or a custom profile, followed by `· Standard` or `· Passthrough` when that profile uses one of those modes. The profile's name is under it. - **Attacks · 24h**: how many attack events on the address started in the last 24 hours. - **Manage**: opens the address's **Shield** tab, where its mode, rate limits and rules are. ## Pages in this section - [How DDoS Shield works](/docs/ddos-shield/how-ddos-shield-works/): what DDoS Shield checks, and how profiles, protection modes, rules and rate limits decide what passes. - [Change a floating IP's DDoS protection](/docs/floating-ips/shield-settings/): give one address its own profile, then set its mode, limits and rules. - [Read attack events](/docs/ddos-shield/attack-events/): what DDoS Shield dropped, when, how much and why. - [Order and use a custom profile](/docs/ddos-shield/custom-profiles/): share one profile between addresses, and block traffic sources. - [Game server protection](/docs/ddos-shield/game-protection/): how to protect a game server hosted here or elsewhere. - [TCP proxy protection](/docs/ddos-shield/tcp-proxy/): how a web proxy or a join address hides your server's own address. - [Get help during an attack](/docs/ddos-shield/under-attack/): what to do while an attack is taking a service down. The public [DDoS Mitigation](https://www.coritan.com/security/ddos) page describes DDoS Shield for buyers. It mentions IPv6 and GRE, but DDoS Shield filters IPv4 only, and it drops GRE in the **Standard** and **Custom** modes. The page's **Order a Shield profile** button opens the order page for a custom profile, and **Discuss Protection Requirements** opens the contact page. ## With the API `GET /api/v1/client/shield/status` returns every floating IP on your account with the profile that filters it: ```bash curl https://api.coritan.com/api/v1/client/shield/status \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "ips": [ { "ip_service_id": 214, "inventory_id": 5120, "address": "203.0.113.10", "prefix_len": 32, "version": "ipv4", "attached": true, "attachment": {"target_type": "vps", "target_service_id": 1042}, "protection": { "mode": "custom", "profile_id": 57, "profile_slug": "user-18-ip-214", "profile_name": "Customer IP 214", "protection_mode": "custom" } }, { "ip_service_id": 215, "inventory_id": 5121, "address": "203.0.113.11", "prefix_len": 32, "version": "ipv4", "attached": false, "attachment": null, "protection": { "mode": "platform_default", "profile_id": 1, "profile_slug": "platform-standard", "profile_name": "Platform Standard", "protection_mode": "standard" } } ], "protect_unattached": true } ``` The list holds each floating IP that we have assigned to you and that is not cancelled, oldest first. | Field | Meaning | | --- | --- | | `ip_service_id` | The floating IP's service ID. The other DDoS Shield requests take it. | | `address`, `prefix_len` | The address, and its prefix length: `32` for a single address, less for a subnet. | | `attached`, `attachment` | Whether the floating IP is attached, and to what: `target_type` is `vps` for an instance or `container` for a server, and `target_service_id` is that service's ID. `attachment` is `null` when nothing is attached. | | `protection.mode` | `platform_default`, or `custom` for an address with its own profile or a custom profile. | | `protection.profile_id`, `protection.profile_slug`, `protection.profile_name` | The profile that filters the address. | | `protection.protection_mode` | That profile's mode: `standard`, `custom` or `passthrough`. | `protect_unattached` is always `true`, because DDoS Shield filters floating IPs that are not attached as well. The [DDoS Shield API reference](/docs/api/reference/client/shield/#op-get-api-v1-client-shield-status) lists every field. ## API - `GET /api/v1/client/shield/status`: Protection view for every assigned floating IP on this account (https://www.coritan.com/docs/api/reference/client/shield/#op-get-api-v1-client-shield-status) # How DDoS Shield works > How DDoS Shield decides which traffic reaches a floating IP, through protection modes, profiles, rules and rate limits. Source: https://www.coritan.com/docs/ddos-shield/how-ddos-shield-works/ DDoS Shield filters the traffic sent to your floating IPs. It runs at Coritan's points of presence, where traffic enters our network, so it drops an attack before the attack reaches your instance or server. It is on for every floating IP on your account from the moment we assign it, whether or not the floating IP is attached. This page explains what DDoS Shield checks and what decides the outcome for each address. [Change a floating IP's DDoS protection](/docs/floating-ips/shield-settings/) shows how to change the settings. ## What DDoS Shield covers | Traffic to | Filtered by DDoS Shield | |---|---| | A floating IP attached to an instance or a server | Yes | | A floating IP that is not attached | Yes | | The IPv4 address that comes with a Cloud Compute instance | Yes | | Each host address of a subnet | Yes | | The shared address of a Container Apps server | No | The address that comes with a Cloud Compute instance is a floating IP, so DDoS Shield filters it like any other. A subnet has one set of settings for all of its hosts ([How subnets work](/docs/floating-ips/subnets/)). A Container Apps server without a floating IP is reached on the shared address of its machine. That traffic goes to the machine directly and does not pass through DDoS Shield ([How the Coritan network works](/docs/platform/network/#traffic-to-a-shared-address)). To put a server behind DDoS Shield, attach a floating IP to it. Protection belongs to the floating IP, not to the machine. When you move a floating IP to another instance or server, its settings move with it ([Attach and detach a floating IP](/docs/floating-ips/attach-and-detach/)). DDoS Shield does not filter the traffic your instance or server sends. Your DDoS Shield settings also do not apply to join addresses and web proxies: those run on Coritan's edge, which protects them in its own way ([Game server protection](/docs/ddos-shield/game-protection/), [TCP proxy protection](/docs/ddos-shield/tcp-proxy/)). ## Filtering is always on Every packet meets the same checks at all times. Nothing switches on when an attack starts, and there is nothing to request: the checks apply from the first packet of an attack, and a rate limit applies as soon as a sender goes over it. When DDoS Shield drops traffic to an address at a high rate, it records an *attack event*. The **Events** tab of DDoS Shield lists them ([Read attack events](/docs/ddos-shield/attack-events/)). An event is a record of what we dropped, and it does not change how we filter. ## Profiles A *profile* holds the settings that decide what happens to traffic for an address: its protection mode, its rate limits, its rules and what happens when no rule matches. Each address uses one profile at a time. *Platform default profile* : The profile every address starts on. We set it, and you cannot change it. *Per-address profile* : A profile of the address's own, which **Customise** on the address's **Shield** tab creates at no charge. Its settings apply to that address alone ([Give the address its own profile](/docs/floating-ips/shield-settings/#give-the-address-its-own-profile)). *Custom profile* : A paid service that holds one set of settings for every address you bind to it, and adds blocked sources ([Order and use a custom profile](/docs/ddos-shield/custom-profiles/)). ## Protection modes A profile's *protection mode* sets how much of the filtering applies to its addresses: | Mode | Packet checks | Rate limits | Rules and blocked sources | |---|---|---|---| | **Standard** | Yes | The platform's | Yes | | **Custom** | Yes | The profile's | Yes | | **Passthrough** | No | No | No | In every mode, **Passthrough** included, we drop packets that cannot be real traffic: a malformed IPv4 header, a packet whose sender is the address it is sent to, and a packet to or from a broadcast or multicast address, or from `0.0.0.0`. These drops do not appear as attack events. > [!WARNING] > With **Passthrough**, an attack on the address reaches your instance or server in full. ## What DDoS Shield drops In **Standard** and **Custom**, traffic to an address goes through three stages in this order. A packet that one stage drops never reaches the next, so a rule cannot let through a packet that a check or a rate limit dropped. 1. Packet checks drop traffic that attacks use and normal traffic does not: - packets that carry IP options - packets whose time to live (TTL) is too low - packets from addresses that are never used on the internet, such as `10.0.0.0/8` and `192.168.0.0/16` - IP fragments - protocols other than TCP, UDP and ICMP, such as GRE and ESP - TCP packets with a combination of flags that no real connection sends, such as SYN with FIN - UDP packets with an oversized payload - UDP packets sent from the ports that reflection attacks use ([Reflection ports](#reflection-ports)) - packets whose TCP, UDP or ICMP header is cut short 2. Rate limits drop what a sender, or all senders together, send over a limit ([Rate limits](#rate-limits)). 3. Rules decide what happens to the rest. Sources that a custom profile blocks are dropped first. Then the most specific rule that matches decides, and **When no rule matches** decides for traffic that no rule matches ([How rules are applied](/docs/floating-ips/shield-settings/#how-rules-are-applied)). Traffic that passes all three stages goes on to your instance or server. We drop the rest without a reply to the sender. You cannot turn a single check off. **Passthrough** turns all of them off at once, with the rate limits and the rules. ## Rate limits Each address has four rate limits: | Limit | What it counts | |---|---| | Per source | Packets from one sender to the address. | | Aggregate | All packets to the address together. | | SYN per source | New TCP connections from one sender. | | ICMP per source | Pings and other ICMP packets from one sender. | In **Custom**, the profile's limits apply, and you can change them ([Rate limits](/docs/floating-ips/shield-settings/#rate-limits)). In **Standard**, the platform's own limits apply instead. A limit allows short bursts of up to twice its rate and drops the rest. A platform-wide limit also caps the packets one sender may send to all the addresses we filter together, whatever each address's profile says. Traffic over it appears in attack events as `global_rate_limit`. ## Reflection ports In a *reflection attack*, the attacker sends small requests to public services, such as DNS or NTP servers, with your address forged as the sender. The services send their much larger answers to your address, and the answers come from the service's own port. In **Standard** and **Custom**, DDoS Shield therefore drops every UDP packet sent to your address from one of the ports below. Your rules cannot let these packets through. The list is about the port a packet comes from. A service on your address that listens on one of these ports, such as a game server on `27015`, still receives its players, whose packets come from other ports. Replies to requests that your machine sends from the floating IP to these ports are dropped, though, so a DNS lookup, an NTP time sync or an IPsec or OpenVPN tunnel on these ports fails ([Troubleshooting](/docs/floating-ips/shield-settings/#troubleshooting)). | Port | Service | |---|---| | `17` | QOTD (Quote of the Day) | | `19` | Chargen | | `53` | DNS | | `111` | Portmap (rpcbind) | | `123` | NTP | | `137` | NetBIOS Name Service | | `161` | SNMP | | `389` | CLDAP | | `500` | ISAKMP (IPsec key exchange) | | `520` | RIP | | `1194` | OpenVPN | | `1434` | Microsoft SQL Server Browser | | `1900` | SSDP (UPnP) | | `3283` | Apple Remote Desktop | | `3702` | WS-Discovery | | `4500` | IPsec NAT traversal | | `5353` | mDNS | | `5683` | CoAP | | `10001` | Ubiquiti discovery | | `11211` | Memcached | | `17185` | VxWorks debug agent (wdbrpc) | | `27015` | Source Engine query | | `37810` | Dahua DVR | If a service on the address needs one of these ports, [contact support](/docs/support/conversations/). ## Limits - DDoS Shield filters IPv4 traffic. The addresses you order in the dashboard are IPv4 only. - It reads packet headers: addresses, ports, protocols, flags and sizes. It does not look inside connections, so it cannot stop an attack made of requests that look real, such as a flood of HTTP requests to a website. A web proxy checks each HTTP request with its web application firewall and turns away clients that send too many ([How web proxies work](/docs/proxies/web-proxies/)). - You cannot change the platform default profile, the packet checks or the list of reflection ports. # Read attack events > See the attacks DDoS Shield detected on your addresses, how large they were and what it did about them. Source: https://www.coritan.com/docs/ddos-shield/attack-events/ In the dashboard: - /dashboard/ddos/events: https://www.coritan.com/dashboard/ddos/events When DDoS Shield drops traffic to one of your floating IPs at a high rate, it records an *attack event*: which address was hit, why we dropped the traffic, how large the attack was and how long it lasted. The **Events** tab of DDoS Shield lists these events, and each one opens to show the senders and a few sampled packets. ## Before you begin - Events exist only for floating IPs. DDoS Shield does not filter a server's shared address, so attacks on it leave no event ([How DDoS Shield works](/docs/ddos-shield/how-ddos-shield-works/#what-ddos-shield-covers)). - An address in **Passthrough** mode records no events, because we drop almost nothing on it. - The events of a floating IP disappear from the list when you cancel the floating IP. ## How we record an event An event records traffic that DDoS Shield dropped for one reason, such as a reflection port or a rate limit, on one address. The routers at our points of presence that filter traffic, which the dashboard calls *scrubbing nodes*, each record their own events. One attack can therefore show as several events: one for each reason, each address and each node that received it. - An event starts once a node drops at least 100 packets per second, or 1 Mbps, of one kind of traffic to the address. We filter smaller amounts all the same, but they make no event. - Drops by the SYN or ICMP rate limits make an event only when they come from 10 or more senders, or reach 20,000 packets per second, so one busy client does not show as an attack. - An event ends once that kind of traffic has stopped for about 45 seconds, so its duration includes those seconds. - When the same kind of traffic hits the same address at the same node again, we update the earlier event instead of adding a new one. The event keeps its first start time, its peak becomes the highest seen, and it keeps its earlier end time until the new attack ends. **Recorded** shows when the event last changed. ## Find an event 1. In the [dashboard](https://www.coritan.com/dashboard/ddos/events), select **DDoS Shield**, then the **Events** tab. 2. Choose how far back to look: **24 hours**, **7 days**, **30 days** or **All time**. The tab opens on **7 days**, and the window goes by when each event first started. 3. To narrow the list, choose an address in the **All addresses** list or a type in the **All attack types** list. Each row shows: - **Started**: the date and time the event started, and how long ago that was. - **Address**: the address that was attacked. For a subnet, it is the host. - **Attack**: the type of traffic we dropped, such as `Reflection` or `SYN rate limit`, with the drop reason under it. - **Peak**: the highest rate in packets per second, with bits per second under it. - **Duration**: how long the event lasted. While the event has no end time, the duration keeps counting. - **Status**: `Mitigating` while the event has no end time, and `Mitigated` once it has one. The tab loads the 200 newest events across your addresses. When the filters match none of them, it shows **No matches**, and **Reset filters** clears both lists and sets the window to **All time**. When there is no event at all, it shows **No attacks yet**. ## Read an event's details Select a row to open it, and select it again to close it. On a phone, each event is a card: select **Show details**. **What happened** : - **Drop reason**: why we dropped the traffic, as in [Attack types](#attack-types). - **Why**: the same reason as a code, such as `udp_src_port_in_reflection_blocklist`. - **Ports hit**: the dashboard shows `—` here. The API's `payload.top_src_ports` lists the busiest source ports ([With the API](#with-the-api)). - **Unique sources**: how many different senders the node saw among the packets it sampled. - **Peak**: the highest packets and bits per second in any one second at that node. The bits per second can be an estimate from the packet rate and the average packet size. - **Duration**: as in the list. - **Volume**: the peak rate multiplied by the duration. It is an upper estimate, because an attack rarely stays at its peak. - **Scrubbing node**: the number of the node that recorded the event. - **Recorded**: when we last updated the event. **Heaviest sources** : A row for each of the busiest senders, up to five. The dashboard shows `—` in place of each address and rate, so read the senders from the API's `top_sources` and `payload.top_sources`. **Sampled packets** : Up to eight packets the node sampled, each as `source:port → destination:port`, such as `198.51.100.20:53 → 203.0.113.10:41822`. ## Attack types The dashboard writes each type in words, such as `SYN rate limit` for `syn_rate_limit`, and the **Why** row shows its code: | Type | Why code | What we dropped | |---|---|---| | `reflection` | `udp_src_port_in_reflection_blocklist` | UDP packets from a port that reflection attacks use ([Reflection ports](/docs/ddos-shield/how-ddos-shield-works/#reflection-ports)). | | `syn_rate_limit` | `syn_rate_limit_exceeded` | New TCP connections from one sender over the SYN limit. | | `icmp_rate_limit` | `icmp_rate_limit_exceeded` | ICMP from one sender over the ICMP limit. | | `customer_rate_limit` | `customer_rate_limit_exceeded` | Packets from one sender over the address's per-source limit. | | `customer_aggregate` | `customer_aggregate_rate_exceeded` | Traffic to the address over its aggregate limit. | | `global_rate_limit` | `global_rate_limit_exceeded` | Packets from one sender over the platform-wide limit. | | `firewall_deny` | `firewall_deny` | Packets stopped by a **Drop** rule, a blocked source, or **When no rule matches** set to **Drop**. | | `firewall_rate_limit` | `firewall_rate_limit` | Packets over the limit of a **Rate limit** rule. | | `fragment` | `fragment_drop` | IP fragments. | | `invalid_protocol` | `proto_not_allowed` | Protocols other than TCP, UDP and ICMP. | | `tcp_flags` | `invalid_tcp_flags` | TCP packets with a combination of flags that no real connection sends. | | `udp_oversized` | `udp_payload_oversized` | UDP packets with an oversized payload. | | `bogon` | `bogon_source` | Packets from addresses that are never used on the internet. | | `ttl_too_low` | `ttl_below_min` | Packets whose time to live was too low. | | `ip_options` | `ip_options_present` | Packets that carried IP options. | | `malformed` | `malformed_packet` | Packets whose TCP, UDP or ICMP header was cut short. | ## What to do about an event - An event means we dropped the traffic. When your service keeps working, there is nothing to do. - When the service is down while an attack goes on, follow [Get help during an attack](/docs/ddos-shield/under-attack/). - When an event shows that we dropped traffic you want, such as `firewall_deny` for your own users or a rate limit that a busy service goes over, change the address's rules or limits on its **Shield** tab ([Change a floating IP's DDoS protection](/docs/floating-ips/shield-settings/)). Limits of your own apply only in **Custom** mode. - When `reflection` events hit the replies to DNS or NTP requests that your machine sent, see [Reflection ports](/docs/ddos-shield/how-ddos-shield-works/#reflection-ports). ## Troubleshooting An attack took a service down, and no event shows : The traffic went to a server's shared address, the address is in **Passthrough**, or the attack passed our checks and limits because it looked like real traffic. An earlier event of the same kind may also have taken the attack in, as [How we record an event](#how-we-record-an-event) explains: choose **All time** and look for a recent **Recorded** time. Then follow [Get help during an attack](/docs/ddos-shield/under-attack/). One attack shows as several events : Each scrubbing node records its own event for each reason and each address. Compare their **Started** times to see which belong together. An event shows `Mitigating` long after the attack ended : The dashboard shows `Mitigating` for any event without an end time, and an event can stay without one when its node stopped sending updates. The API's `active` field turns `false` once we have had no update for two minutes, and that is the reliable sign that the event is over. **Heaviest sources** or **Ports hit** shows only `—` : The dashboard does not read these parts of the event. Read `top_sources`, `payload.top_sources` and `payload.top_src_ports` from the API. The list stops before the event you want : The tab loads the 200 newest events. Ask the API for more with a higher `limit`. **Could not load the events** : Select **Try again**. If it keeps failing, [contact support](/docs/support/conversations/). ## Related - [How DDoS Shield works](/docs/ddos-shield/how-ddos-shield-works/) - [Change a floating IP's DDoS protection](/docs/floating-ips/shield-settings/) - [Get help during an attack](/docs/ddos-shield/under-attack/) - [Order and use a custom profile](/docs/ddos-shield/custom-profiles/) ## With the API `GET /api/v1/client/shield/events` returns the attack events on your floating IPs, newest first. `limit` sets how many come back, 50 by default, and each floating IP contributes at most 500. Send a `limit` of 1 or more. ```bash curl "https://api.coritan.com/api/v1/client/shield/events?limit=200" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "events": [ { "id": 90211, "node_id": 3, "session_key": "214:7:cb00710a", "subject_id": 214, "subject_kind": "service", "ip_service_id": 214, "inventory_id": 5120, "address": "203.0.113.10", "attack_type": "reflection", "drop_reason_id": 7, "drop_reason": "reflection", "pps": 184000, "bps": 1510000000, "unique_sources": 3120, "top_sources": "[\"198.51.100.20\", \"198.51.100.21\"]", "started_at": "2026-09-25T14:02:11", "ended_at": "2026-09-25T14:09:56", "received_at": "2026-09-25T14:09:56", "active": false, "payload": "{\"classification\": \"policy_drop\", \"why_hint\": \"udp_src_port_in_reflection_blocklist\"}" } ] } ``` The example shortens `top_sources` and `payload`. Both arrive as JSON text, so parse them before you read them. | Field | Meaning | | --- | --- | | `address`, `ip_service_id` | The address that was attacked, and the service ID of its floating IP. For a subnet, `address` is the host. | | `attack_type`, `drop_reason` | Why we dropped the traffic, as in [Attack types](#attack-types). `drop_reason_id` is the same reason as a number. | | `pps`, `bps` | The peak packets and bits per second in one second at the node. `bps` can be an estimate from the packet rate and the average packet size. | | `unique_sources` | How many different senders the node saw among the packets it sampled. | | `top_sources` | Up to five of the busiest senders' addresses. | | `started_at`, `ended_at` | When the event started and ended, in UTC. `ended_at` is `null` until the event ends. | | `received_at` | When we last updated the event, in UTC. | | `active` | `true` while the event has no end and we updated it in the last two minutes. | | `node_id` | The scrubbing node that recorded the event. | `subject_id`, `subject_kind`, `session_key` and `inventory_id` identify the event on our side. `payload`, once parsed, holds the node's details: ```json { "classification": "policy_drop", "drop_reason_id": 7, "drop_reason": "reflection", "effective_mode": "standard", "top_sources": [{"ip": "198.51.100.20", "packets": 41}, {"ip": "198.51.100.21", "packets": 39}], "top_src_ports": [{"port": 53, "packets": 7410}, {"port": 123, "packets": 1590}], "top_protocols": [{"proto": 17, "packets": 9000}], "samples": [ {"src_ip": "198.51.100.20", "dst_ip": "203.0.113.10", "ip_proto": 17, "src_port": 53, "dst_port": 41822, "ttl": 54, "pkt_len": 1242, "tcp_flags": null, "udp_len": 1208} ], "why_hint": "udp_src_port_in_reflection_blocklist" } ``` | Field | Meaning | | --- | --- | | `effective_mode` | The protection mode that applied: `standard` or `custom`. | | `top_sources` | Up to five of the busiest senders, each with how many of the sampled packets came from it. | | `top_src_ports` | Up to five of the busiest source ports, with their sampled packets. | | `top_protocols` | Up to five IP protocols by number, such as `17` for UDP, with their sampled packets. | | `samples` | Up to eight sampled packets, with their addresses, protocol, ports, TTL, length in bytes, TCP flags and UDP length. | | `why_hint` | The code that the **Why** row shows. | The example leaves out `threshold_snapshot`, which records the node's own settings. The [DDoS Shield API reference](/docs/api/reference/client/shield/#op-get-api-v1-client-shield-events) lists every field. ## API - `GET /api/v1/client/shield/events`: Recent attack events for this account's IP services (https://www.coritan.com/docs/api/reference/client/shield/#op-get-api-v1-client-shield-events) # Order and use a custom profile > Order a custom DDoS Shield profile, bind your floating IPs to it, and block or unblock traffic sources on them. Source: https://www.coritan.com/docs/ddos-shield/custom-profiles/ In the dashboard: - /dashboard/ddos/profiles: https://www.coritan.com/dashboard/ddos/profiles - /dashboard/order/ddos-shield: https://www.coritan.com/dashboard/order/ddos-shield A *custom profile* is a DDoS Shield profile that you order as a service. It holds one protection mode, one set of rate limits and rules, and one choice of what happens when no rule matches, for every floating IP you bind to it. It also lets you block traffic sources on those addresses. The **Profiles** tab of DDoS Shield lists your custom profiles. An address can also have a profile of its own, at no charge, from its **Shield** tab ([Change a floating IP's DDoS protection](/docs/floating-ips/shield-settings/)). A custom profile is for addresses that should share their settings, and for blocking sources. ## Before you begin - A custom profile filters only the floating IPs you bind to it. It cannot filter a server's shared address ([How DDoS Shield works](/docs/ddos-shield/how-ddos-shield-works/#what-ddos-shield-covers)). - An address uses one profile at a time. When you bind an address that has a profile of its own, it moves to the custom profile, and the rules of its own profile stop applying to it. - Every address you bind shares the profile's settings. A change on the **Shield** tab of one of them applies to all of them. - The plans' descriptions name packet rates, but every plan starts with the same rate limits as any new profile ([Rate limits](/docs/floating-ips/shield-settings/#rate-limits)). Set the limits you need yourself, as [Change the settings](#change-the-settings) shows. - For the first invoice, credit on your account pays first. Otherwise you pay by card or PayPal on the page the order lands on ([How the first payment works](/docs/get-started/order-a-service/#how-the-first-payment-works)). ## Order a custom profile Open the order page in one of these ways: - In the sidebar, select **DDoS Shield**, then **Order custom profile** at the top of the page. The button is on every tab, and the **Profiles** tab repeats it while you have no custom profile. - On [**Order a service**](https://www.coritan.com/dashboard/order), select the **DDoS Shield** card under **Network and security**. The page has three numbered sections, and the summary under **Your order** sits beside them. On a phone the summary follows the sections, and **Review order** at the bottom of the screen takes you to it. 1. Under **Plan**, choose a plan. Each card shows the plan's description and its price. 2. Choose the billing cycle with the buttons beside the **Plan** heading, such as **Monthly**. A longer term names what it saves, such as `Annually · save 20%`. 3. Under **Profile**, enter a **Profile name** of up to 100 characters, such as `Game servers`. The profile's title on the **Profiles** tab is made from it, and the **Shield** tab of each bound address shows it. When you leave it empty, we name the profile `Custom Shield` followed by its service ID. 4. Choose the **Protection mode**. **Custom**, which the page starts on, applies the profile's own rate limits. **Standard** applies the platform's limits with your rules added ([Protection modes](/docs/ddos-shield/how-ddos-shield-works/#protection-modes)). The order page does not offer **Passthrough**. 5. Under **When no rule matches**, choose **Allow**, which the page starts on, or **Drop**. The line under the buttons says what your choice does, such as `Traffic passes unless a rule drops it.` 6. Under **Addresses to protect**, tick each floating IP that the profile should filter. The line under an address says where it is now: `On the platform scrubbing today.`, or `It has a custom profile already. Binding moves it to this one.` for an address with a profile of its own or on another custom profile. You can leave every box empty and bind addresses later. 7. Check the summary. It lists the plan with its billing cycle, the **Protection mode** and the **Addresses**, such as `2 addresses` or `None yet; bind them later`, with **Change** beside each to go back to its section. The **Total** shows the price and what is due today, and the line under it says how the first invoice is paid. 8. Select the button under the summary. It reads **Place order and pay** when you pay after placing the order, and **Place order** when your credit pays for it or your account is billed in arrears. While the plan cannot be ordered, the button is greyed out and the line under it says why, such as `This plan is sold out right now.` If we refuse the order, **Could not place the order** appears above the button with the reason, and your choices stay as they were. Once you place it, the order's own page opens. When its title is **Order placed, payment due**, pay under **Pay invoice** with account credit, a saved card or PayPal account, or a new card ([Pay and follow the order](/docs/get-started/order-a-service/#pay-and-follow-the-order)). To pay later, leave the page and pay the invoice under [Invoices](/docs/billing/invoices/). We set the profile up once its invoice is paid, and we bind the addresses you ticked. Until then, its card on the **Profiles** tab shows **Setting up**, and you cannot bind addresses to it. The order's page updates on its own. Its title becomes **Your order is ready** when the profile is active, and `Open profile` at the top of the page then opens DDoS Shield. ### Add a profile to another order The order pages for Cloud Compute, Container Apps and Floating IPs offer a **DDoS Shield profile** card under **Add-ons** ([Add products to the order](/docs/get-started/order-a-service/#add-products-to-the-order)). A profile you add there differs from one you order on its own page: - It protects the addresses in that order. On an instance, those are its included public IPv4 and any floating IPs you add. On a server, it is the dedicated IPv4 address you add. On a floating IP order, it is the new address. - It starts in **Custom** mode, with **Allow** when no rule matches. - Its **Profile name** takes up to 40 characters. When you leave it empty, the profile takes the instance's or server's name. - Until the order has an address, the card's switch stays off and the card says why: `Keep the included public IPv4 first. The profile protects addresses you own.` on Cloud Compute, or `Add the dedicated IPv4 address first. The profile protects addresses you own.` on Container Apps. Once it is set up, the profile appears on the **Profiles** tab like any other. ## Read the Profiles tab The tab shows a card for each custom profile that has not ended. With none, it shows **No custom DDoS profiles yet** and **Order custom profile**. A profile's card shows: - Its slug as the title, such as `game-servers-1043-a1b2`: the name in lower case with hyphens, the service ID and four random characters. Until we set the profile up, the title is the plan's name. - The plan, its price per billing cycle and the next renewal date, with the service's status, such as `Active`. - The protection mode that the profile was ordered with, such as `Custom mode`. The **Shield** tab of a bound address shows the mode in use. - **Bound addresses**: each address bound to the profile, which links to its **Shield** tab, with **Unbind…** beside it. With none, the card says `No address is bound yet. The profile protects nothing until you bind one.` - **Bind an address** and **Block a source**, which the sections below describe. - **Open Billing**, which opens the service's **Billing** tab. Under the cards, **Per-address profiles** lists each address that has a profile of its own, with that profile's name and mode. **Manage** opens the address's **Shield** tab. ## Bind an address 1. On the profile's card, choose the address under **Bind an address**. 2. Select **Bind IP**. A message confirms `IP bound to custom profile`, and the address appears under **Bound addresses**. DDoS Shield then filters it with the profile's settings. The list offers only your own floating IPs that are on the platform default profile. When none is left, the card says `Every address on the account is already on a custom profile.` To move an address from another custom profile, unbind it there first. To move an address that has a profile of its own, bind it with `bind_ip` ([With the API](#with-the-api)). ## Unbind an address 1. Under **Bound addresses**, select **Unbind…** beside the address. 2. Read the dialog and select **Unbind**. A message confirms `IP unbound; uses platform default scrubbing`. The address goes back to the platform default profile, and the profile's rules and limits stop applying to it. Blocks made on the address stay in place, and this profile can no longer remove them. Remove them before you unbind the address, as [Block a source](#block-a-source) explains. The address goes back to the platform default profile even when it had a profile of its own before you bound it. In that case, **Customise** on the address's **Shield** tab brings its own profile back, with the settings and rules it had. ## Change the settings The profile's protection mode, rate limits, rules and **When no rule matches** are on the **Shield** tab of each address bound to it. 1. Under **Bound addresses**, select an address. Its **Shield** tab opens, with the profile's name on the profile card. 2. Change the profile or its rules as [Change a floating IP's DDoS protection](/docs/floating-ips/shield-settings/#change-the-profile) shows. > [!IMPORTANT] > A change on the **Shield** tab of one bound address applies to every address bound to the profile. To aim a rule at one of the addresses, enter that address as the rule's **Destination prefix**, such as `203.0.113.10/32`. The rule then matches only traffic to that address. The rate limits apply to each bound address on its own. An aggregate limit of 10,000,000 packets per second lets each address receive up to that much, rather than all of them together. ## Block a source We drop every packet from a blocked source before any rule decides, so an **Allow** rule cannot let it through. You block a source on one of the addresses bound to the profile, so bind an address first. 1. On the profile's card, enter the sender's address under **Block a source**, such as `198.51.100.7`. 2. When more than one address is bound, choose under **On** the address that the block is on. 3. Select **Block source**. A message confirms `Blocked 198.51.100.7`. A block works differently from a rule: - It is on one of the profile's addresses: the one you choose under **On**, or the only one bound. - It blocks one sender address. The field also takes a prefix, such as `198.51.100.0/24`, but a prefix blocks nothing. To drop a range, add a **Drop** rule with the range as its **Source prefix** ([Add a rule](/docs/floating-ips/shield-settings/#add-a-rule)). - It never expires. - It stays in place when you unbind its address and when the profile ends. - The card does not list your blocks and cannot remove one. Remove a block with `whitelist_ip` and the block's `id`, which the answer to `blacklist_ip` gives ([With the API](#with-the-api)). The profile must be active, and the block's address must still be bound to it. For a block you made in the dashboard, [contact support](/docs/support/conversations/) with the address. ## Suspension, plan changes and cancelling A custom profile is billed like any other service ([Invoices](/docs/billing/invoices/)). ### While the profile is suspended We suspend the profile when an invoice for it stays unpaid ([Failed payments and suspended services](/docs/billing/failed-payments/#a-service-is-suspended)). While it is suspended: - We set the profile to **Passthrough**, so DDoS Shield stops filtering its addresses and an attack reaches them in full. - **Bind IP**, **Unbind…** and **Block source** fail, with a message that ends `Service is not active`. Pay the open invoice to lift the suspension. We then set the profile back to the mode its card shows, such as `Custom mode`, or to **Custom** when the card shows `Passthrough mode`. If you changed the mode on a **Shield** tab since the order, set it again there. ### Change the billing cycle You can move a profile to another billing cycle of its plan on its **Billing** tab, but not to another plan, because each plan is a product of its own ([Change a service's plan](/docs/billing/change-plan/)). To move to another plan, order a new profile, bind your addresses to it and cancel the old one. > [!WARNING] > Changing the billing cycle resets part of the profile. It sets **Packets per second per source** and **Aggregate packets per second** to the plan's own values, and binds the addresses you chose in the order again, including any you have unbound since. It can also set the protection mode, **When no rule matches** and the name back to what you ordered. Check the profile and its bound addresses afterwards. ### Cancel the profile Select **Open Billing** on the profile's card and cancel the service there ([Cancel a service](/docs/billing/cancel-a-service/)). When the service ends: - Every bound address goes back to the platform default profile, and the profile's rules stop applying. - The card leaves the **Profiles** tab. - Your blocks stay in place, and nothing can remove them with this profile any more. Remove them with `whitelist_ip` before the service ends. ## Troubleshooting The card shows **Setting up** : The order waits for payment. Pay its invoice, and we set the profile up once it is paid. The card shows `Profile is failed`, or `Profile is provisioning` for a long time : We could not set the profile up. [Contact support](/docs/support/conversations/) with the plan and the time you ordered. An address is missing from **Addresses to protect** : The list holds the floating IPs we have assigned to you. An address that you ordered but that is not ready yet is left out. Order the profile without it, and bind it once it is ready. **Could not load your addresses** : The list of floating IPs did not load, and the message gives the reason. Reload the page, or order the profile and bind addresses once it is set up. **Could not place the order** with `ip_service_ids must name your own floating IPs that are not terminated or cancelled.` : An address you ticked ended after the page loaded. The message ends with its service ID, such as `Remove 214.` Reload the page and tick the addresses again. `No DDoS Shield plans yet` : DDoS Shield is not in the catalogue for your account. [Contact support](/docs/support/conversations/) if you expected it. **Could not load the plans** : The plans did not load. Select **Try again**, or reload the page. An address is missing from **Bind an address** : It has a profile of its own, or it is on another custom profile. [Bind an address](#bind-an-address) explains how to move it. An address you ticked in the order is not under **Bound addresses** : We skip an address that we cannot bind when we set the profile up, such as one that was cancelled in the meantime. Bind it now. `Could not bind IP: Service is not active`, or the same message for another action : The profile is suspended, or we have not set it up yet. Pay its open invoice. `Bind an address to this profile first, then block the source on it.` : No address is bound to the profile, so there is nothing to block the source on. Bind an address, then block the source. `This profile protects more than one address. Set ip_service_id to the one to block the source on.` : The block did not say which address it is on. Choose an address under **On**, then select **Block source** again. The profile's rate limits have no effect : The profile is in **Standard** mode, which uses the platform's limits. Set **Protection mode** to **Custom** on the **Shield** tab of a bound address. A blocked sender still reaches an address : The address is in **Passthrough**, or you blocked a prefix, which blocks nothing. Add a **Drop** rule for the range. ## Related - [How DDoS Shield works](/docs/ddos-shield/how-ddos-shield-works/) - [Change a floating IP's DDoS protection](/docs/floating-ips/shield-settings/) - [Read attack events](/docs/ddos-shield/attack-events/) - [Order a service](/docs/get-started/order-a-service/) - [Cancel a service](/docs/billing/cancel-a-service/) ## With the API ### Order a profile with the API The custom profile plans are the products with `module_name` `antiddos` in [`GET /products/`](/docs/api/reference/client/catalog/#op-get-api-v1-products). Order one with [`POST /services/order`](/docs/api/reference/client/services/#op-post-api-v1-services-order), as [Order a service](/docs/get-started/order-a-service/#with-the-api) explains: ```bash curl -X POST https://api.coritan.com/api/v1/services/order \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "product_id": 41, "pricing_id": 118, "config": { "profile_name": "Game servers", "protection_mode": "custom", "default_action": "allow", "ip_service_ids": [214, 215], "per_source_pps": 50000 } }' ``` | Key in `config` | Meaning | | --- | --- | | `profile_name` | The profile's name, 191 characters or fewer. It defaults to `Custom Shield` followed by the service ID. | | `protection_mode` | `standard`, `custom` or `passthrough`. It defaults to `custom`. | | `default_action` | `allow`, or `deny` for **Drop**. It defaults to `allow`. | | `ip_service_ids` | The service IDs of the floating IPs to bind. Each must be one of your own floating IPs that is not terminated or cancelled. | | `per_source_pps`, `aggregate_pps` | The profile's starting per-source limit (100–10,000,000) and aggregate limit (10,000–100,000,000). Leave them out to start at the values in [Rate limits](/docs/floating-ips/shield-settings/#rate-limits). | A `config` that fails a check answers `422` with `{"detail": {"errors": [...]}}`, such as `protection_mode must be standard|custom|passthrough`, `profile_name must be text of 191 characters or fewer`, or `ip_service_ids must name your own floating IPs that are not terminated or cancelled. Remove 902.` The API does not check the two limits, so keep them within their ranges. When you send a `hostname`, the card shows it as the title in place of the slug. A profile takes no add-ons. To add a profile to an order for an instance, a server or a floating IP, put it in that order's `addons`, with an optional `profile_name` of up to 40 characters in its `config`. The order names the addresses itself, so an add-on whose `config` has `ip_service_ids` answers `422` with `Shield Custom Profile protects the addresses in this order, so leave ip_service_ids out of its config.` ([Add-ons in the order](/docs/get-started/order-a-service/#add-ons-in-the-order)). The answer's `service.id` is the profile's service ID. Once we have set the profile up, [`GET /services/{service_ref}`](/docs/api/reference/client/services/#op-get-api-v1-services-service-ref) returns its `module_data`, with the profile's `profile_id` and `profile_slug`. The addresses bound to it are the ones whose `protection.profile_id` in `GET /api/v1/client/shield/status` is that `profile_id` ([With the API](/docs/ddos-shield/#with-the-api)). ### Run an action [`POST /services/{service_ref}/actions`](/docs/api/reference/client/services/#op-post-api-v1-services-service-ref-actions) runs an action on the profile, with `service_ref` set to its service ID. Each action reaches only your own floating IPs and the addresses bound to this profile: ```bash curl -X POST https://api.coritan.com/api/v1/services/1043/actions \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"action": "bind_ip", "params": {"ip_service_id": 216}}' ``` ```json {"success": true, "message": "IP bound to custom profile", "data": {"ip_service_id": 216, "inventory_id": 5122, "profile_id": 58, "enabled": true, "address": "203.0.113.12"}} ``` Every answer has `success`, `message` and `data`. An action that fails can still answer `200`, with `"success": false` and the reason in `message`, so check `success`. | `action` | `params` | What it does | | --- | --- | --- | | `bind_ip` | `ip_service_id` | Binds one of your own floating IPs that is not terminated or cancelled, and moves it off the profile it was on. `data` is the binding. | | `unbind_ip` | `ip_service_id` | Moves an address bound to this profile back to the platform default profile. `data` is `null`. | | `blacklist_ip` | `ip_address`, `ip_service_id` when more than one address is bound, and optionally `reason` | Blocks a source on one of the profile's addresses. `data.block` is the new block, with its `id`. | | `whitelist_ip` | `block_id` | Removes a block whose address is still bound to this profile. `data` is `null`. | | `view_alerts` | None | Returns up to 50 recent attack events for the addresses bound now, in `data.events`. | With no address bound, `view_alerts` answers `"success": true` with an empty `data.events` and `No address is bound to this profile, so it has no attack events.` Read attack events with `GET /api/v1/client/shield/events` instead, because it covers every address on your account ([Read attack events](/docs/ddos-shield/attack-events/#with-the-api)). To block a source, send one IPv4 address, and the `ip_service_id` of the bound address to block it on: ```bash curl -X POST https://api.coritan.com/api/v1/services/1043/actions \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"action": "blacklist_ip", "params": {"ip_address": "198.51.100.7", "ip_service_id": 214, "reason": "UDP flood"}}' ``` ```json { "success": true, "message": "Blocked 198.51.100.7", "data": { "block": {"id": 881, "address": "198.51.100.7", "prefix_len": 32, "scope": "subject", "ip_service_id": 214, "reason": "UDP flood", "source": "manual", "expires_at": null, "created_at": "2026-09-26T09:12:40"} } } ``` Keep the block's `id`, because you need it to remove the block and no request lists your blocks. We store `ip_address` as you send it without checking it, and an address we cannot read, or a prefix, blocks nothing. You can leave out `ip_service_id` while one address is bound. To remove the block, while address `214` is still bound to the profile: ```bash curl -X POST https://api.coritan.com/api/v1/services/1043/actions \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"action": "whitelist_ip", "params": {"block_id": 881}}' ``` ```json {"success": true, "message": "Removed block 881", "data": null} ``` The answers you can act on: | Answer | Meaning | | --- | --- | | `400` `Service is not active` | The profile is not set up yet, is suspended, or has ended. | | `400` `Action 'name' not available` | `action` is not one of the five above. | | `404` `Assigned IP service '216' not found` | `bind_ip` named an ID that is not one of your floating IPs, or one that is terminated or cancelled. Or `unbind_ip` or `blacklist_ip` named an address that is not bound to this profile. | | `404` `Blocked source '881' not found` | `whitelist_ip` named a block that does not exist, or one whose address is not bound to this profile. | | `404` `Service not found` or `403` `Access denied` | `service_ref` is not a service, or not yours. | | `"success": false` with `ip_service_id required` | `bind_ip` or `unbind_ip` came without `ip_service_id`. | | `"success": false` with `ip_address required` | `blacklist_ip` came without `ip_address`. | | `"success": false` with `Bind an address to this profile first, then block the source on it.` | `blacklist_ip` ran on a profile with no address bound. | | `"success": false` with `This profile protects more than one address. Set ip_service_id to the one to block the source on.` | `blacklist_ip` came without `ip_service_id`, and more than one address is bound. | | `"success": false` with `Pass block_id to remove a blocked source entry` | `whitelist_ip` came without `block_id`. | The [services API reference](/docs/api/reference/client/services/#op-post-api-v1-services-service-ref-actions) lists every field. ## API - `POST /api/v1/services/order`: Order a platform service, and any add-ons bought with it (https://www.coritan.com/docs/api/reference/client/services/#op-post-api-v1-services-order) - `POST /api/v1/services/{service_ref}/actions`: Execute action (https://www.coritan.com/docs/api/reference/client/services/#op-post-api-v1-services-service-ref-actions) # Game server protection > How Coritan protects game servers, including ones you host elsewhere, and where to set it up. Source: https://www.coritan.com/docs/ddos-shield/game-protection/ In the dashboard: - /security/game-protection: https://www.coritan.com/security/game-protection Coritan protects a game server in one of two ways, depending on how players reach it: - A *join address* puts Coritan's edge between the players and the server. Players connect to the edge at a name such as `survival-smp.coritan.gg`, and the edge checks each connection before it forwards it. Players never see the server's own address. - *DDoS Shield* filters all the traffic sent to a floating IP. It protects a server that players reach at its address, whatever the game. A server can have both. Players who use the join address then reach it through the edge, and players who type its floating IP reach it through DDoS Shield. ## Choose the protection for your game | Your game server | Protection | Where to set it up | |---|---|---| | Minecraft Java or Bedrock, hosted here | A join address | [Create a join address](/docs/proxies/join-addresses/create-a-join-address/) | | Minecraft Java or Bedrock, hosted elsewhere | An external server, which gives it a join address | [Connect a server you host elsewhere](/docs/proxies/external-servers/connect-an-external-server/) | | Another game that connects over TCP, hosted here | A **TCP service** join address | [Create a join address](/docs/proxies/join-addresses/create-a-join-address/) | | A game that connects over UDP, such as Rust, Counter-Strike 2 or Valheim, hosted here | DDoS Shield on a floating IP | [DDoS Shield on a game server](#ddos-shield-on-a-game-server) | | Another game, hosted elsewhere | — | [A game hosted elsewhere](#a-game-hosted-elsewhere) | A Minecraft or TCP game server that has a floating IP gets DDoS Shield as well, for the players who join at the address. ## How a join address protects a server Players connect to the edge, and the edge opens its own connection to your server ([How join addresses work](/docs/proxies/join-addresses/)). On the way, the edge: - limits how many connections each address may have open at once, and blocks for a while an address that opens too many in a short time; - for Minecraft Java, turns away each address's first join while the edge is under a flood of connections, and lets the next one through, so a flood of single connections does not reach your server; - closes a Minecraft Java connection that does not start with a valid Minecraft handshake; - for Minecraft, answers players' server lists from a copy of your server's status that it keeps for a few seconds, so a flood of server list requests stops at the edge. [What players see](/docs/proxies/join-addresses/#what-players-see) lists the messages players get when the edge turns them away. A **TCP service** closes the connection with no message, because a plain TCP connection has no way to show one. The edge does not sign players in or check their accounts. Your server still does that, as it would without the edge. A **Minecraft Bedrock** join address carries only NetherNet, which Bedrock uses from version 26.50, so older Bedrock clients cannot join through it ([Editions and ports](/docs/proxies/join-addresses/#editions-and-ports)). ### Keep the server's own address private A join address protects a server only from attackers who do not know the server's own address. An attack sent straight to that address does not pass through the edge. - Give players the join address only. Take the server's own address out of server lists, websites and DNS records. - For a server you host elsewhere, allow only the **Edge addresses** through its firewall on the game's port ([Set up your server](/docs/proxies/external-servers/connect-an-external-server/#set-up-your-server)). If attackers already know the server's address, move the server to a new address before you give out the join address. - For a server hosted here, attach a floating IP, so that DDoS Shield filters the traffic sent to the server's address as well. Traffic sent to the shared address of the server's machine still does not pass through DDoS Shield ([How the Coritan network works](/docs/platform/network/#traffic-to-a-shared-address)). ## DDoS Shield on a game server DDoS Shield filters every packet sent to a floating IP at Coritan's points of presence, for any game and any port ([How DDoS Shield works](/docs/ddos-shield/how-ddos-shield-works/)). There is nothing to turn on: a floating IP is protected from the moment we assign it. - The address that comes with a Cloud Compute instance is a floating IP, so DDoS Shield already filters it. - A Container Apps server needs a floating IP attached ([Attach and detach a floating IP](/docs/floating-ips/attach-and-detach/)). Its ports and join addresses move to the floating IP, and a Minecraft server can also answer on the game's default port ([Publish a port on the default game port](/docs/managed-containers/ports/#publish-a-port-on-the-default-game-port)). For a game server, DDoS Shield: - drops traffic that games do not send, such as IP fragments, protocols other than TCP, UDP and ICMP, and UDP answers from the ports that reflection attacks use. A game server that listens on one of those ports, such as `27015`, still receives its players ([Reflection ports](/docs/ddos-shield/how-ddos-shield-works/#reflection-ports)); - limits how fast each sender, and all senders together, may send to the address ([Rate limits](/docs/ddos-shield/how-ddos-shield-works/#rate-limits)); - applies the rules you add on the address's **Shield** tab ([Add a rule](/docs/floating-ips/shield-settings/#add-a-rule)); - records what it drops as attack events on the **Events** tab of DDoS Shield ([Read attack events](/docs/ddos-shield/attack-events/)). Rules that often help a game server: - A **Rate limit** rule for **UDP** on the game's port limits what each player's address may send, such as `2000` packets per second to port `19132`. - **When no rule matches** set to **Drop**, with an **Allow** rule for each port the game uses, stops traffic to every other port before it reaches the server. Add **Allow** rules for the UDP replies the machine expects too ([When no rule matches](/docs/floating-ips/shield-settings/#when-no-rule-matches)). DDoS Shield reads packet headers only. It cannot tell a real player from a program that speaks the game's protocol, and it does not hide the server's address. ## A game hosted elsewhere An external server gives a Minecraft Java or Bedrock server that you run elsewhere a join address, as a plan you pay for ([How external servers work](/docs/proxies/external-servers/)). A Bedrock server must accept NetherNet connections. To order one, select **New proxy…** on the [Edge Proxy](https://www.coritan.com/dashboard/proxies/game) page, choose the edition and then **A server elsewhere**, which opens the order page ([Order an external server](/docs/proxies/external-servers/connect-an-external-server/#order-an-external-server)). You cannot set up protection yourself for any other game that runs elsewhere. A **TCP service** join address forwards only to a server hosted here, and DDoS Shield filters only Coritan's floating IPs. Move the game to a [Cloud Compute](/docs/cloud-compute/) instance, or ask the sales team ([Reach sales and the other teams](/docs/support/#reach-sales-and-the-other-teams)). ## The Game Protection page The public [Game Protection](https://www.coritan.com/security/game-protection) page describes game server protection for buyers. Some of what it says differs from what you can set up: - It describes a protection profile for each game. There are no profiles for particular games: every join address gets the checks above, and DDoS Shield applies the same checks to every address, with the rules you add. - It lists Minecraft Bedrock as RakNet. A join address carries Bedrock over NetherNet only. - Most of the titles it lists, such as Rust, Counter-Strike 2 and Valheim, connect over UDP. A join address cannot carry them, so they rely on DDoS Shield on a floating IP. - It says we check each connection so that only real game clients get in. The edge checks connections as [How a join address protects a server](#how-a-join-address-protects-a-server) describes, but it does not sign players in. - It says monitoring alerts you to attacks. We send no alerts. Attack events on your floating IPs appear on the **Events** tab. - It says a server can go live as `play.you.coritan.gg`. A join address is one name under `coritan.gg`, such as `survival-smp.coritan.gg`. For a name such as `play.alex.coritan.gg`, claim a free coritan.gg name and link `play` under it to the join address ([Use your own domain for a join address](/docs/proxies/join-addresses/custom-domain/)). **Connect your own server** on the page opens the order page for an external server. **Request Custom Protection** and **Partner With Us** open the contact page, whose form sends nothing, so email [sales@coritan.com](mailto:sales@coritan.com) instead. ## Related - [How join addresses work](/docs/proxies/join-addresses/) - [How external servers work](/docs/proxies/external-servers/) - [How DDoS Shield works](/docs/ddos-shield/how-ddos-shield-works/) - [Change a floating IP's DDoS protection](/docs/floating-ips/shield-settings/) - [TCP proxy protection](/docs/ddos-shield/tcp-proxy/) # TCP proxy protection > How Coritan's proxies hide your server's own address and check traffic on its way to your server. Source: https://www.coritan.com/docs/ddos-shield/tcp-proxy/ In the dashboard: - /security/tcp-proxy: https://www.coritan.com/security/tcp-proxy A Coritan proxy stands between your service and the people who use it. Clients connect to Coritan's edge, and the edge opens its own connection to the server that runs the service, which we call the *origin*. Clients see only the edge's address, so a flood aimed at your hostname lands on the edge, which forwards only the connections and requests that pass its checks. ## The three kinds of proxy | Proxy | Carries | Origin | Set it up | |---|---|---|---| | Web proxy | HTTP and HTTPS for a hostname | A server hosted here, or any public address | [Create a web proxy](/docs/proxies/web-proxies/create-a-web-proxy/) | | Join address | Minecraft Java, Minecraft Bedrock, or another game over TCP as a **TCP service** | A server hosted here | [Create a join address](/docs/proxies/join-addresses/create-a-join-address/) | | External server | Minecraft Java or Minecraft Bedrock | A server you host elsewhere | [Connect a server you host elsewhere](/docs/proxies/external-servers/connect-an-external-server/) | You create each of them with **New proxy…** on the [Edge Proxy](https://www.coritan.com/dashboard/proxies) page of the dashboard. A join address is a name under `coritan.gg`. A web proxy serves a domain you hold, or a name under a free coritan.gg name that you claim ([Claim a free coritan.gg name](/docs/proxies/coritan-gg-names/)). ## What the edge checks - A web proxy checks each request with its web application firewall, which blocks a request that looks like an attack or asks the visitor to prove they are human. It also answers `429` to a client that sends too many requests, and `413` to a request body over the limit ([How the edge handles a request](/docs/proxies/web-proxies/#how-the-edge-handles-a-request)). - A join address or an external server limits the connections from each player's address. For Minecraft Java it also checks the handshake and turns away first joins during a flood ([How a join address protects a server](/docs/ddos-shield/game-protection/#how-a-join-address-protects-a-server)). - A **TCP service** gets the same limits on each address's connections, and closes a connection over a limit without a message. The edge does not read the traffic inside the connection. The edge's addresses are Coritan's, so your DDoS Shield settings do not apply to the traffic sent to a proxy. ## What your origin receives Every connection reaches the origin from one of the edge's addresses. The origin learns the client's own address only from what the edge passes on: | Proxy | Where the client's address arrives | |---|---| | Web proxy | The `X-Forwarded-For` and `X-Real-IP` headers, and a PROXY protocol header with **PROXY protocol to origin** on | | Minecraft Java join address or external server | A PROXY protocol header with **PROXY protocol** on, or else the Minecraft handshake | | **TCP service** | A PROXY protocol header with **PROXY protocol** on | | Minecraft Bedrock join address or external server | Nowhere, so the origin sees the edge's address for every player | The edge sends PROXY protocol version 2. Turn it on only when the origin expects the header, or every connection fails. [What your origin receives](/docs/proxies/web-proxies/#what-your-origin-receives) and [What your server receives](/docs/proxies/join-addresses/#what-your-server-receives) give the details. ## Keep the origin's address private A proxy protects the origin only from attackers who do not know the origin's own address. A flood sent straight to that address does not pass through the edge. - Point your public names at the proxy only. Remove DNS records that point at the origin's address, including the records of other services on the same machine. - If the origin's address has been public, attackers may still know it. Move the origin to a new address that you do not publish. - For an external server, allow only the **Edge addresses** through the firewall on the game's port ([Set up your server](/docs/proxies/external-servers/connect-an-external-server/#set-up-your-server)). - An origin on a Cloud Compute instance has a floating IP, so DDoS Shield also filters the traffic sent straight to it. The shared address of a Container Apps server is not filtered ([How the Coritan network works](/docs/platform/network/#traffic-to-a-shared-address)). ## The TCP Proxy page The public [TCP Proxy](https://www.coritan.com/security/tcp-proxy) page describes the proxies for buyers. Some of what it says differs from what you can set up: - Its four protocol handlers are the web proxy for HTTP and HTTPS, and the three editions of a join address: Minecraft Java, Minecraft Bedrock and **TCP service**. - It describes Bedrock filtering as RakNet. A join address carries Bedrock over NetherNet only, for Bedrock 26.50 and newer. - Its Custom TCP handler is the **TCP service**. It forwards only to a server hosted here, on an edge port that Coritan picks, and has no settings for connection limits, bandwidth or timeouts. - It lists GeoIP filtering and access lists. No proxy can allow or refuse clients by country. - It lists PROXY protocol versions 1 and 2. The edge sends version 2 only. - It lists load balancing with health checks. A web proxy can spread requests across several origins, and skips one that keeps failing, but it does not check their health in advance ([Origins](/docs/proxies/web-proxies/#origins)). - It says every account includes a free coritan.gg name, such as `www.you.coritan.gg`. You claim the name first, then use the names under it for web proxies and join addresses ([Claim a free coritan.gg name](/docs/proxies/coritan-gg-names/)). **Set up a proxy** on the page opens the **Game** tab of [Edge Proxy](https://www.coritan.com/dashboard/proxies/game). **Get Protected** and **Contact Sales** open the contact page, whose form sends nothing. To ask about a TCP service that runs elsewhere, email [sales@coritan.com](mailto:sales@coritan.com). ## Related - [How web proxies work](/docs/proxies/web-proxies/) - [How join addresses work](/docs/proxies/join-addresses/) - [How external servers work](/docs/proxies/external-servers/) - [Game server protection](/docs/ddos-shield/game-protection/) - [How DDoS Shield works](/docs/ddos-shield/how-ddos-shield-works/) # Get help during an attack > What to do when an address of yours is under attack right now, and how to reach Coritan quickly. Source: https://www.coritan.com/docs/ddos-shield/under-attack/ In the dashboard: - /under-attack: https://www.coritan.com/under-attack Use this page while an attack is taking one of your services down. Tell us about the attack first, so that we know about it while you work. Then follow the steps for the address that the attack reaches. Most of them are changes you can make yourself in the dashboard. ## Tell us about the attack 1. In the [dashboard](https://www.coritan.com/dashboard/support), select **Support**, then **New conversation…**. 2. In **Subject**, name the service and say that it is under attack, such as `survival-smp under attack`. 3. Set **Priority** to Critical, and choose the service under **About a service**. 4. In **Message**, write what [What to include](#what-to-include) lists, then select **Start conversation**. 5. Call [+1 (302) 606-0504](tel:+13026060504), and give the conversation's number, such as `#1042`. If you cannot sign in, email [support@coritan.com](mailto:support@coritan.com) and call the same number. [Contact support from the dashboard](/docs/support/conversations/#start-a-conversation) covers the form in full. Do not use the form on our website's attack page, which [The attack page](#the-attack-page) describes. It sends nothing. ## Find what the attack reaches What you can do depends on the address that the attack is sent to: | Your service is reached at | Filtered by | Steps | |---|---|---| | A floating IP, including the address that comes with a Cloud Compute instance | DDoS Shield | [An attack on a floating IP](#an-attack-on-a-floating-ip) | | The shared address of a Container Apps server | — | [An attack on a shared address](#an-attack-on-a-shared-address) | | A join address or a web proxy | Coritan's edge | [An attack on a join address or a web proxy](#an-attack-on-a-join-address-or-a-web-proxy) | | An address with another provider | — | [A service hosted elsewhere](#a-service-hosted-elsewhere) | The **Addresses** card on the **Overview** tab of [DDoS Shield](https://www.coritan.com/dashboard/ddos) lists every floating IP on your account, with what each one is **Attached to**. The address of a server that is not on that list is a shared address. ## An attack on a floating IP DDoS Shield already filters every packet sent to the address ([Filtering is always on](/docs/ddos-shield/how-ddos-shield-works/#filtering-is-always-on)). Check what it drops, then tighten the address's settings if the attack still gets through. 1. In the [dashboard](https://www.coritan.com/dashboard/ddos/events), select **DDoS Shield**, then the **Events** tab, and choose the address in the **All addresses** list. An event whose **Status** is `Mitigating` has not ended. Select it to see the **Attack** type, the **Peak** rate, the **Unique sources** and the **Sampled packets** ([Read attack events](/docs/ddos-shield/attack-events/)). 2. Open the address's **Shield** tab: on the **Overview** tab of DDoS Shield, select **Manage** on the address's row. If the profile's mode is **Passthrough**, set **Protection mode** to **Standard** or **Custom**, then select **Save profile** ([Change the profile](/docs/floating-ips/shield-settings/#change-the-profile)). 3. If the service is still down, add rules that fit it, so that less of the attack gets through ([Add a rule](/docs/floating-ips/shield-settings/#add-a-rule)): - Add an **Allow** rule for each port your service uses, then set **When no rule matches** to **Drop**. Traffic to every other port then stops at our points of presence. Allow the UDP replies that your machine expects as well ([When no rule matches](/docs/floating-ips/shield-settings/#when-no-rule-matches)). - Add a **Rate limit** rule on the service's port, so that each sender can send only so much to it. - When a few senders send most of the traffic, add a **Drop** rule for each one, with its address as the **Source prefix**, such as `198.51.100.7/32`. The event's **Sampled packets** show senders, and the API's `top_sources` lists the busiest ([Read attack events](/docs/ddos-shield/attack-events/#with-the-api)). - In **Custom** mode, lower the rate limits that the attack goes over, such as **Packets per second per source** ([Rate limits](/docs/floating-ips/shield-settings/#rate-limits)). A change reaches our filtering within a minute, as the profile card says. Add what you changed to the conversation. DDoS Shield reads packet headers only, so a flood of HTTP requests that look real passes it. For a website, put the site behind a web proxy, whose web application firewall checks each request and turns away clients that send too many ([Create a web proxy](/docs/proxies/web-proxies/create-a-web-proxy/)). ## An attack on a shared address DDoS Shield does not filter the shared address of a Container Apps server. An attack on it reaches the machine that runs your server and the other servers on that machine, and none of your settings can filter it. 1. Tell us about the attack, as [Tell us about the attack](#tell-us-about-the-attack) describes, with the shared address and the port. 2. Attach a floating IP to the server, so that players have an address that DDoS Shield filters ([Attach and detach a floating IP](/docs/floating-ips/attach-and-detach/)). If you have none, order one first ([Order a floating IP](/docs/floating-ips/order-a-floating-ip/)). The server's ports and join addresses move to the floating IP, and a running server may restart. 3. Give players the floating IP, or the server's join address, in place of the shared address. Attaching a floating IP does not stop the attack on the shared address, which still reaches the machine. ## An attack on a join address or a web proxy A join address and a web proxy resolve to Coritan's edge, so an attack on the name lands on the edge. The edge checks that traffic itself, and your DDoS Shield settings do not apply to it ([What the edge checks](/docs/ddos-shield/tcp-proxy/#what-the-edge-checks)). - When players see `Please rejoin to verify your connection.`, the edge is checking joins. Players should join again ([What players see](/docs/proxies/join-addresses/#what-players-see)). - When the name stops answering, tell us, with the name and the time it stopped. - When the attack reaches your server anyway, the attackers know the server's own address. Follow the steps above for that address, and keep it private from then on ([Keep the origin's address private](/docs/ddos-shield/tcp-proxy/#keep-the-origin-s-address-private)). ## A service hosted elsewhere We cannot filter a service where another provider hosts it. You can put a proxy in front of two kinds of service, with a [Coritan account](/docs/get-started/create-an-account/): - A website can sit behind a web proxy, with the web application firewall ([Create a web proxy](/docs/proxies/web-proxies/create-a-web-proxy/)). - A Minecraft Java or Bedrock server can take a join address through an external server ([Connect a server you host elsewhere](/docs/proxies/external-servers/connect-an-external-server/)). A proxy stops an attack aimed at the name. An attack sent to the service's own address goes around it, so also move the service to a new address that you do not publish ([Keep the origin's address private](/docs/ddos-shield/tcp-proxy/#keep-the-origin-s-address-private)). For any other service, or to protect address ranges of your own, email [sales@coritan.com](mailto:sales@coritan.com) or call [+1 (302) 606-0504](tel:+13026060504) ([Bare metal, colocation and IP transit](/docs/platform/sales-led-products/)). ## What to include In the conversation, or when you call, give: - the address or name under attack, and the port when the attack is on one; - when the attack started, with your time zone; - what stopped working, and for whom; - the **Started** time and **Attack** type of each event on the **Events** tab, if there are any; - what you have changed since the attack started. ## The attack page The [attack page](https://www.coritan.com/under-attack) on our website, which **Under Attack?** at the top of the website opens, has a form for an attack in progress. The form sends nothing. **Request Emergency Mitigation** shows **We have received your request**, but the request reaches nobody, and nobody calls you back. Tell us as [Tell us about the attack](#tell-us-about-the-attack) describes instead. The page gives times to detect an attack and to start mitigating it. For your floating IPs there is nothing to start, because DDoS Shield applies its checks to every packet all the time. ## Related - [Support](/docs/support/) - [Read attack events](/docs/ddos-shield/attack-events/) - [Change a floating IP's DDoS protection](/docs/floating-ips/shield-settings/) - [How DDoS Shield works](/docs/ddos-shield/how-ddos-shield-works/) - [TCP proxy protection](/docs/ddos-shield/tcp-proxy/) ## With the API Open the conversation with [`POST /chat/conversations`](/docs/api/reference/client/support/#op-post-api-v1-chat-conversations), with `priority` set to `critical` and the service's ID in `service_id` ([Create a conversation](/docs/support/conversations/#create-a-conversation)): ```bash curl -X POST https://api.coritan.com/api/v1/chat/conversations \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "subject": "203.0.113.10 under attack", "priority": "critical", "service_id": 214, "body": "UDP flood to 203.0.113.10 port 27015 since 14:02 UTC. Players cannot connect. No change on our side." }' ``` `service_id` here is the floating IP's service ID, the `ip_service_id` that `GET /api/v1/client/shield/status` returns ([With the API](/docs/ddos-shield/#with-the-api)). Then follow the attack and change the address's settings: - `GET /api/v1/client/shield/events` returns the events, and an event's `active` is `true` while it goes on ([Read attack events](/docs/ddos-shield/attack-events/#with-the-api)). - `PATCH /api/v1/client/shield/subjects/{ip_service_id}/profile` changes the mode, the rate limits and what happens when no rule matches, and `POST /api/v1/client/shield/subjects/{ip_service_id}/rules` adds a rule ([Change a floating IP's DDoS protection](/docs/floating-ips/shield-settings/#with-the-api)). To drop one sender: ```bash curl -X POST https://api.coritan.com/api/v1/client/shield/subjects/214/rules \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "drop-198-51-100-7", "action": "deny", "src_prefix": "198.51.100.7/32"}' ``` # Snapshots > Full archives of your servers kept on your account, so you can restore them onto any server after the original is gone. Source: https://www.coritan.com/docs/snapshots/ In the dashboard: - /dashboard/snapshots: https://www.coritan.com/dashboard/snapshots A *snapshot* is a full archive of a server's files, kept in your account's snapshot storage. It belongs to your account rather than to the server it came from, so it stays after the server is gone and you can restore it onto any of your servers. Snapshots have replaced server backups, which belonged to one server ([Snapshots and backups compared](/docs/snapshots/snapshots-and-backups/)). ## The Snapshots page The **Snapshots** page in the [dashboard](https://www.coritan.com/dashboard/snapshots) lists every snapshot on your account, from every server you have had. You take a snapshot on the server's own **Snapshots** tab ([Take a server snapshot](/docs/managed-containers/snapshots/)). - The **Snapshot storage** card shows how much of the account's allowance is **Used**, how much is left, and how much each service adds. When the account holds more than its allowance, the card shows **Over the allowance** with the date we start deleting snapshots. - **All**, **Ready**, **In progress**, **Failed**, **Locked** and **Deleted servers** filter the list. **Search snapshots** matches a snapshot's name, server, software, status or location. - The table shows each **Snapshot**, the **Server** it came from, its **Status**, its **Size** and when it was **Taken**, newest first. Select a column heading to sort by it. - The menu at the end of each row restores, downloads, locks and deletes the snapshot. Under a snapshot's name, the row can show the software it was taken on, a **Locked** badge, and the error of a failed snapshot. Under the date, it says who took it when you did not: a schedule's name, **By the platform** or **By support**. A snapshot of a server that is gone carries a **Deleted server** badge. The page refreshes by itself while a snapshot is being taken, restored or deleted. ## What you can do Take and restore: - [Take a server snapshot](/docs/managed-containers/snapshots/), by hand or on a [schedule](/docs/managed-containers/schedules/) - [Restore a snapshot onto a server](/docs/snapshots/restore-a-snapshot/), the one it came from or another - [Start a new server from a snapshot](/docs/snapshots/new-server-from-a-snapshot/) Look after them: - [Lock, download and delete snapshots](/docs/snapshots/manage-snapshots/) - [How snapshot storage works](/docs/snapshots/storage-allowance/), and what happens when the account is over its allowance - [Order more snapshot storage](/docs/snapshots/add-snapshot-storage/) - [Snapshots and backups compared](/docs/snapshots/snapshots-and-backups/), with the snapshots we take before we remove a server ## How snapshots are billed Each server plan includes snapshot storage, and the plans on your account add up to one allowance. Extra storage is a service of its own with its own price and billing cycle ([Order more snapshot storage](/docs/snapshots/add-snapshot-storage/)). [Billing](/docs/billing/) explains invoices and cancellation. ## Limits - A snapshot holds the server's files only. The server's [databases](/docs/managed-containers/databases/) are not in it. - A server runs one snapshot or restore at a time. - You cannot take or restore a snapshot while the server is installing, being moved or suspended. - A free server includes enough storage for one snapshot, and you cannot download a snapshot of a free server. - [Server limits and rate limits](/docs/managed-containers/limits/#snapshots) lists the rest. ## With the API `GET /api/v1/client/snapshots` lists every snapshot on your account, the same list the **Snapshots** page shows. ```bash curl https://api.coritan.com/api/v1/client/snapshots \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json [ { "id": 3187, "uuid": "5f0c9a2e-7b1d-4e8a-9c3f-2a6d8e1b4c70", "name": "before-1.21-update", "kind": "container", "status": "completed", "size_bytes": 1932735283, "is_locked": true, "created_by": "customer", "source_uuid": "1a2b3c4d-5e6f-4a8b-9c0d-1e2f3a4b5c6d", "source_name": "survival-smp", "source_deleted": false, "software_label": "Paper 1.21.4", "created_at": "2026-09-16T14:30:00", "completed_at": "2026-09-16T14:34:12" } ] ``` Each snapshot also carries these fields: `status` : `pending` or `creating` while we take it, `completed` when you can restore or download it, `restoring` or `deleting` while that work runs, or `failed`. `created_by` : `customer` for a snapshot you took, `schedule` for one a schedule took, with its `schedule_uuid` and `schedule_name`, or `system` for one we took before removing a server. On those, `reason` is `safety_cancel`, `safety_unpaid` or `safety_reclaim`. `error` and `hold_reason` : Why a snapshot failed, and why we put it on hold. You cannot delete a snapshot that is on hold. `source_server_name`, `source_location` and `source_used_bytes` : The server's current name, where it ran and how much disk it used. `source_server_name` is `null` once the server is gone, while `source_name` keeps the name it had. `software`, `software_slug` and `software_version` : What the server ran when we took the snapshot. `software.inferred` is `true` when we recorded it later, from what the server ran afterwards. `region`, `checksum` and `checksum_type` : Where the archive is stored, and its checksum. `GET /api/v1/client/snapshots/{snapshot_uuid}` returns one snapshot with the same fields. A snapshot that is not on your account answers `404` with `Snapshot 5f0c9a2e-7b1d-4e8a-9c3f-2a6d8e1b4c70 not found`. ## API - `GET /api/v1/client/snapshots`: Every snapshot this account holds, across every server it has had (https://www.coritan.com/docs/api/reference/client/container-apps/snapshots/#op-get-api-v1-client-snapshots) - `GET /api/v1/client/snapshots/{snapshot_uuid}`: One snapshot, for the order form that was handed its uuid in a link (https://www.coritan.com/docs/api/reference/client/container-apps/snapshots/#op-get-api-v1-client-snapshots-snapshot-uuid) # Lock, download and delete snapshots > Find a snapshot on the Snapshots page, lock it against accidental deletion, download the archive or delete it. Source: https://www.coritan.com/docs/snapshots/manage-snapshots/ In the dashboard: - /dashboard/snapshots: https://www.coritan.com/dashboard/snapshots The **Snapshots** page lists every snapshot on your account, from every server you have had. The menu at the end of each row locks the snapshot, downloads its archive or deletes it. A server's own **Snapshots** tab has the same menu for the snapshots in its list. ## Before you begin - Only the account that holds a snapshot can lock, download or delete it. Someone you share a server with cannot, even with the Snapshots permission ([Share a server with other users](/docs/managed-containers/users/#choose-permissions)). - You can download a snapshot once its status is `Completed`. - The free plan does not include downloads, so you cannot download a snapshot of a free server. ## Find a snapshot 1. In the [dashboard](https://www.coritan.com/dashboard/snapshots), select **Snapshots** in the sidebar. 2. To see one group, select **Ready**, **In progress**, **Failed**, **Locked** or **Deleted servers**. **All** shows every snapshot again. 3. To search, type part of a snapshot's name, its server, its software, its status or its location in **Search snapshots**. 4. To sort the list, select a column heading. The list starts with the newest snapshot first. When nothing matches, the page shows **No matches**. Select **Reset filters** to clear the search and the filter. ## Lock or unlock a snapshot Open the menu at the end of the snapshot's row and select **Lock**. A **Locked** badge shows beside the snapshot's name. To remove the lock, select **Unlock** in the same menu. While a snapshot is locked: - Nobody can delete it from the dashboard or the API. - A schedule never deletes it to make room for a new snapshot. - If we took it before suspending a server, we keep it when the server is unsuspended ([Snapshots and backups compared](/docs/snapshots/snapshots-and-backups/#snapshots-we-take-for-you)). **Lock** and **Unlock** are greyed out while the snapshot is being taken, restored or deleted. > [!IMPORTANT] > A lock does not keep a snapshot past the storage cleanup. When an account stays over its snapshot allowance after the cleanup date, we delete snapshots until it fits, and we delete locked snapshots after the unlocked ones. [How snapshot storage works](/docs/snapshots/storage-allowance/#when-the-account-is-over) explains the cleanup. ## Download a snapshot 1. Open the menu at the end of the snapshot's row and select **Download**. It is greyed out until the snapshot is `Completed`. 2. The download opens in a new browser tab. The file is a `.tar.gz` archive named after the snapshot's ID. The link works for 6 hours by default. To download the snapshot again later, select **Download** again for a new link. You can download a snapshot while an invoice is unpaid. A snapshot of a free server cannot be downloaded while that server is on the free plan. Once the server moves to a paid plan, or once it is deleted, you can download its snapshots. Until then, restore the snapshot onto a server, or download the files you need from the server's **Files** tab. ## Delete a snapshot > [!CAUTION] > Deleting a snapshot deletes its archive from storage. You cannot undo it, and we cannot recover the archive. 1. Open the menu at the end of the snapshot's row and select **Delete snapshot…**. It is greyed out while the snapshot is locked, and while its status is `Creating`, `Restoring` or `Deleting`. 2. Type the snapshot's name to confirm, then select **Delete snapshot**. You can delete a failed snapshot, and one that has stayed on `Pending`. A snapshot we put on hold says so under its name, with the reason, and you cannot delete it until we lift the hold. ## Result - A locked snapshot shows the **Locked** badge, and the **Locked** filter lists it. - A download starts in a new tab. - A deleted snapshot leaves the list, and a message confirms it, such as `before-1.21-update deleted.` The space it used is free again at once, and the **Snapshot storage** card shows it. ## Troubleshooting When an action fails, the page shows **Could not get a download link**, **Could not change the lock** or **Could not delete the snapshot**, with one of these messages: `Downloading a snapshot is not included on the free plan. …` : The snapshot comes from a free server. Restore it onto a server instead, or download the files you need from the server's **Files** tab. `That snapshot is not finished yet` : Wait until the snapshot's status is `Completed`. A failed snapshot has no archive to download. `Object storage did not return a download URL for this snapshot.` : The storage that holds the snapshot did not answer. Try again in a few minutes, or [contact support](/docs/support/conversations/). `Cannot delete a locked snapshot` : Select **Unlock** first, then delete the snapshot. `Cannot delete a snapshot while it is being restored` : Wait until the restore ends and the snapshot's status is `Completed` again. `This snapshot is on hold and cannot be deleted.` : We put the snapshot on hold, and the reason shows under its name. [Contact support](/docs/support/conversations/) about it. `Cannot change the lock while the snapshot is creating` : The message names `pending`, `creating`, `restoring` or `deleting`. Wait until that finishes, then try again. `Snapshot … not found` : The snapshot is gone, or another account holds it. On a server someone shared with you, the snapshots belong to the owner, so only the owner can lock, download or delete them. ## Related - [Snapshots](/docs/snapshots/) - [Restore a snapshot onto a server](/docs/snapshots/restore-a-snapshot/) - [How snapshot storage works](/docs/snapshots/storage-allowance/) - [Take a server snapshot](/docs/managed-containers/snapshots/) ## With the API Each request takes a snapshot's `uuid` from `GET /api/v1/client/snapshots` and works only on your own account's snapshots. Another account's snapshot answers `404` with `Snapshot … not found`. Lock a snapshot, or unlock a locked one: ```bash curl -X POST https://api.coritan.com/api/v1/client/snapshots/$SNAPSHOT/lock \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` The request turns the lock on when it is off, and off when it is on. It returns the snapshot with its new `is_locked`. A snapshot that is being taken, restored or deleted answers `409`. Get a download link: ```bash curl https://api.coritan.com/api/v1/client/snapshots/$SNAPSHOT/download \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` The response is `{"url": "…"}`, a signed link to the archive that works for 6 hours by default. Fetch the link as it is, with no `Authorization` header: ```bash curl -o before-1.21-update.tar.gz "$URL" ``` A snapshot that is not `completed` answers `409`, one from a free server answers `403`, and `503` means the storage did not return a link. Delete a snapshot: ```bash curl -X DELETE https://api.coritan.com/api/v1/client/snapshots/$SNAPSHOT \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` The response is `{"message": "Snapshot deleted"}`. A locked snapshot answers `400`, one on hold answers `403`, and one that is being restored answers `409`. ## API - `POST /api/v1/client/snapshots/{snapshot_uuid}/lock`: Keep a snapshot, or release it (https://www.coritan.com/docs/api/reference/client/container-apps/snapshots/#op-post-api-v1-client-snapshots-snapshot-uuid-lock) - `GET /api/v1/client/snapshots/{snapshot_uuid}/download`: A signed URL for the archive itself (https://www.coritan.com/docs/api/reference/client/container-apps/snapshots/#op-get-api-v1-client-snapshots-snapshot-uuid-download) - `DELETE /api/v1/client/snapshots/{snapshot_uuid}`: Delete a snapshot and the archive behind it (https://www.coritan.com/docs/api/reference/client/container-apps/snapshots/#op-delete-api-v1-client-snapshots-snapshot-uuid) # Restore a snapshot onto a server > Restore a snapshot onto the server it came from or onto any other server on your account. Source: https://www.coritan.com/docs/snapshots/restore-a-snapshot/ In the dashboard: - /dashboard/snapshots: https://www.coritan.com/dashboard/snapshots - /dashboard/servers/…/snapshots: https://www.coritan.com/dashboard/servers Restoring a snapshot writes its files onto a server. The server can be the one the snapshot came from, to roll it back, or any other server on your account, to move a world to it. Start from the account's **Snapshots** page to choose the server, or from the **Snapshots** tab of the server you want to restore onto. ## Before you begin - The server must belong to the account that holds the snapshot. On a server someone shared with you, the Snapshots permission lets you restore that server's snapshots onto it. - Only a snapshot with the status `Completed` can be restored. - The snapshot must come from the same game as the server. When the server runs different software for that game, such as Fabric for a Paper world, you must allow the difference, and the plugins or mods do not carry over. - The server must not be installing, being moved or suspended. A server runs one snapshot or restore at a time. - A restore stops the server, and the server stays stopped until you start it. - A snapshot holds the server's files only. The server's [databases](/docs/managed-containers/databases/) stay as they are. > [!WARNING] > **Wipe the server first** is ticked by default. It deletes every file on the server before we write the snapshot's files, so you lose every change made since the snapshot was taken. [Take a snapshot](/docs/managed-containers/snapshots/) of the server first if you might need its current files. ## Restore from the Snapshots page 1. In the [dashboard](https://www.coritan.com/dashboard/snapshots), go to **Snapshots**. 2. Open the menu at the end of the snapshot's row and select **Restore onto a server…**. It is greyed out until the snapshot is `Completed`. 3. Under **Restore onto**, choose the server. The list starts on the server the snapshot came from, marked `(source)`, while that server exists. 4. Leave **Wipe the server first** ticked to replace the server's files with the snapshot's. Clear it to write the snapshot's files over the ones already there; any file newer than the snapshot stays mixed in with the restored ones. 5. If the server runs different software for the same game, tick **Allow a different loader**. 6. Select **Wipe and restore**, or **Restore snapshot** when you cleared **Wipe the server first**. ## Restore from a server's Snapshots tab 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the server you want to restore onto, then its **Snapshots** tab. 2. Select **This server** for the server's own snapshots, or **Whole account** for every snapshot on your account. On **Whole account**, the **Fit** column says how each snapshot fits this server ([See which snapshots fit this server](/docs/managed-containers/snapshots/#see-which-snapshots-fit-this-server)). 3. Open the menu at the end of the snapshot's row and select **Restore onto this server…**. It is greyed out for a snapshot from another game. 4. Check the details in the dialog. When **Fit** shows **Other loader**, tick **Allow a different loader** to go ahead. 5. Leave **Wipe the server first** ticked, or clear it, as step 4 of the other way describes. 6. Select **Wipe and restore** or **Restore snapshot**. ## Result A message confirms the start, such as `Restore of before-1.21-update onto survival-smp started. The server is stopped while files are written.` The snapshot's status shows `Restoring` while we write the files, and `Completed` again when the restore ends. The server stays stopped. Open its **Console** tab and select **Start**. The snapshot shows `Completed` again after a failed restore too, so check that the server starts and holds the files you expect before players join. ## Troubleshooting When a restore cannot start, the dialog stays open and the page shows **Could not start the restore** with one of these messages: `A snapshot or restore is already in progress on this server; wait for it to finish before you restore.` : Wait until the other snapshot or restore of the server shows `Completed` or `Failed`, then try again. `Cannot restore a snapshot while the server is suspended` : A suspended server cannot take a restore. The same message names `installing` or `transferring` while the server installs or moves; wait until that finishes. `That snapshot belongs to a different account than this server.` : The server you chose was shared with you by someone else. Restore onto a server on your own account. `Snapshot is restoring and cannot be restored` : The message names the snapshot's status. Only a `Completed` snapshot can be restored, so wait for it to finish, or choose another snapshot. A message that ends `Restore it onto a server running the same game.` : The snapshot comes from a different game than the server. The start of the message says what differs. Restore it onto a server that runs the same game. A message that ends `Confirm the software mismatch to restore anyway.` : Tick **Allow a different loader** and try again. If the server's tab does not offer it, restore from the account's **Snapshots** page, where the dialog always offers it. A message that starts `Object storage did not return a download URL` : The storage that holds the snapshot did not answer. Try again in a few minutes. `The node is unreachable` or `The node could not complete this request` : The machine the server runs on did not accept the restore. The snapshot is unharmed and shows `Completed` again. Try again in a few minutes, or [contact support](/docs/support/conversations/). The dialog says `You have no server to restore onto.` : [Order a server](/docs/managed-containers/order-a-server/) first, then restore onto it. The server order page on coritan.com cannot start from a snapshot, even though the dialog says it can. [Start a new server from a snapshot](/docs/snapshots/new-server-from-a-snapshot/) describes what you can do. **Wipe and restore** is greyed out : No server is chosen, the snapshot comes from another game, or **Allow a different loader** is not ticked for a snapshot that needs it. The snapshot stays on `Restoring` : We end a restore that has not finished after 4 hours, and the snapshot shows `Completed` again. Check the server's files, then restore again or [contact support](/docs/support/conversations/). The server does not start after the restore : Read the console output. A world from a newer game version than the server runs does not load, and [The server crashed or stopped](/docs/managed-containers/troubleshooting/#the-server-crashed-or-stopped) covers the other causes. ## Related - [Take a server snapshot](/docs/managed-containers/snapshots/) - [Lock, download and delete snapshots](/docs/snapshots/manage-snapshots/) - [Start a new server from a snapshot](/docs/snapshots/new-server-from-a-snapshot/) - [Snapshots and backups compared](/docs/snapshots/snapshots-and-backups/) ## With the API `POST /api/v1/client/snapshots/{snapshot_uuid}/restore` starts a restore. The body names the server: `target_server_uuid` : Required. The UUID of the server to restore onto. It must belong to the account that holds the snapshot. `truncate` : `true`, the default, deletes the server's files first. `false` writes the snapshot's files over them. `allow_mismatch` : `true` allows a restore onto different software for the same game. The default is `false`. It never allows a different game. ```bash curl -X POST https://api.coritan.com/api/v1/client/snapshots/$SNAPSHOT/restore \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"target_server_uuid": "7e3f1b2a-9c4d-4f6e-8a1b-2c3d4e5f6a7b", "truncate": true, "allow_mismatch": false}' ``` ```json { "message": "Restore initiated", "status": "restoring", "restore_uuid": "c2d4e6f8-1a3b-4c5d-8e9f-0a1b2c3d4e5f" } ``` Poll `GET /api/v1/client/snapshots/{snapshot_uuid}` until `status` is `completed` again, then start the server. A request without `target_server_uuid` answers `422`. A server on another account answers `403`, and so does a server shared with you when you lack `snapshot.restore` on it. A snapshot or server that does not exist answers `404`. The server's state, a snapshot that is not `completed` and a software mismatch answer `409` with the messages under [Troubleshooting](#troubleshooting). `503` means the storage is not available, and `502` means the server's machine did not accept the restore. ## API - `POST /api/v1/client/snapshots/{snapshot_uuid}/restore`: Restore a snapshot onto one of this account's servers (https://www.coritan.com/docs/api/reference/client/container-apps/snapshots/#op-post-api-v1-client-snapshots-snapshot-uuid-restore) # Start a new server from a snapshot > Order a server that is built with a snapshot's software and restored from it as soon as the install finishes. Source: https://www.coritan.com/docs/snapshots/new-server-from-a-snapshot/ In the dashboard: - /dashboard/servers/…/snapshots: https://www.coritan.com/dashboard/servers An order can name one of your snapshots. We then install the new server with the software the snapshot was taken on, and restore the snapshot onto it as soon as the install finishes, so the new server starts as a copy of the old one. Use it to move a world to another location or a larger plan, or to bring back a server you deleted. The server order page on coritan.com has no place to name a snapshot, so you place this order with the API. In the dashboard, [order a server](/docs/managed-containers/order-a-server/) with the same software and then [restore the snapshot onto it](/docs/snapshots/restore-a-snapshot/). ## Before you begin - The snapshot must be on your account, with the status `Completed`. The [Snapshots](https://www.coritan.com/dashboard/snapshots) page lists every snapshot you have. - The plan's disk must hold the restored files. We compare it with the larger of two figures: the snapshot's size, and the disk the old server used when we took the snapshot. - You need an access token for the API ([Authentication](/docs/api/authentication/)), and the `product_id` and `pricing_id` of the plan ([Order a service](/docs/get-started/order-a-service/#with-the-api)). - A snapshot holds the server's files only. Its [databases](/docs/managed-containers/databases/) do not come with it, so plan to create them again on the new server. ## Order a server from a snapshot 1. List your snapshots and copy the `uuid` of the one to start from: ```bash curl https://api.coritan.com/api/v1/client/snapshots \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` Check that its `status` is `completed`. `size_bytes` and `source_used_bytes` show how much disk the new plan needs. 2. Place the order with the snapshot's `uuid` in `config.restore_snapshot_uuid`: ```bash curl -X POST https://api.coritan.com/api/v1/services/order \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "product_id": 12, "pricing_id": 34, "hostname": "survival-smp", "idempotency_key": "4a8e2c61-9f3b-4d7a-b5e0-8c1f2a3d6e97", "config": { "location": "fra", "name": "survival-smp", "specialization_slug": "minecraft-paper", "restore_snapshot_uuid": "5f0c9a2e-7b1d-4e8a-9c3f-2a6d8e1b4c70" } }' ``` Send `specialization_slug` with the software the old server ran. When the snapshot records its software (its `software_slug` is set), we install that software and version and ignore the software fields in the order. When it records none, we install what the order names. 3. When the response has `requires_payment` set to `true`, pay the invoice it names in `invoice_id` ([Pay an invoice](/docs/billing/invoices/#with-the-api)). We start setting up the server once the invoice is paid. ## Follow the restore 1. In the [dashboard](https://www.coritan.com/dashboard/servers), go to **Container Apps** and open the new server, then its **Snapshots** tab. 2. Read the card at the top of the tab. The tab refreshes by itself while the restore waits or runs. - **Waiting to restore your snapshot**: we are still installing the server. - **Restoring your snapshot**: we are writing the snapshot's files onto the server. - **Restored from a snapshot**: the files are in place. The card gives the date. - **Could not restore the snapshot**: the restore failed, and the card gives the reason. Select **Try again** to run it again. 3. When the card shows **Restored from a snapshot**, open the **Console** tab and select **Start**. The server does not start by itself. The restore deletes the files the install wrote and puts the snapshot's files in their place. It goes ahead when the server runs different software for the same game, such as Fabric for a Paper world, but plugins or mods then do not carry over. It never restores a snapshot of a different game. While it runs, the snapshot shows `Restoring` on the **Snapshots** page, and it stays on your account afterwards. To move a world, check that the new server runs as you expect before you [cancel the old one](/docs/billing/cancel-a-service/). ## Result The new server shows **Restored from a snapshot** on its **Snapshots** tab and holds the old server's files. Its address and port are new, so send your players the address that **Copy address** at the top of the server page copies. ## Troubleshooting When the order names a snapshot it cannot use, the API answers `422` and places no order. `detail.errors` holds one of these messages: `That snapshot is not on this account, so a server cannot be created from it.` : The `uuid` is wrong, or another account holds the snapshot. Copy the `uuid` from your own list of snapshots. `That snapshot is creating and cannot be restored yet. Wait for it to finish, then order again.` : The message names the snapshot's status. Wait until it is `completed`, then place the order again. A `failed` snapshot cannot be used. A message that starts `This snapshot needs about` : The plan's disk is too small for the snapshot, for example `This snapshot needs about 2048 MB of disk and this plan has 1024 MB. Pick a larger plan to restore it.` Order a plan with more disk. `Only game server snapshots can seed a new server.` : The snapshot is not of a Container Apps server. `Snapshots are not enabled on this platform yet, so a server cannot be created from one.` : Snapshots are switched off for now. Order the server without `restore_snapshot_uuid`, or [contact support](/docs/support/conversations/). When the restore fails after the order, the card on the **Snapshots** tab shows **Could not restore the snapshot** with one of these reasons: `The snapshot was deleted before it could be restored.` : Someone deleted the snapshot while the server was installing. We cannot bring it back, so **Try again** fails too. Set up the server by hand, or restore another snapshot onto it. `That snapshot is not on this account, so a server cannot be created from it.` : The snapshot was deleted, or no longer qualified, before we set up the server. We built the server anyway, with the software the order carried. Restore another snapshot onto it, or set it up by hand. A reason that starts `The daemon` : The machine the server runs on did not finish the restore. Select **Try again**. If it fails again, [contact support](/docs/support/conversations/). A reason that ends `Restore it onto a server running the same game.` : The snapshot records no software, and the order installed software for a different game. Restore the snapshot onto a server that runs the same game ([Restore a snapshot onto a server](/docs/snapshots/restore-a-snapshot/)). The card stays on **Waiting to restore your snapshot** : The install is still running, or it failed. After a failed install, the restore waits and runs once an install succeeds ([The install failed](/docs/managed-containers/troubleshooting/#the-install-failed)). The server holds the snapshot's files but will not start : Read the console output. [The server crashed or stopped](/docs/managed-containers/troubleshooting/#the-server-crashed-or-stopped) covers the usual causes. ## Related - [Restore a snapshot onto a server](/docs/snapshots/restore-a-snapshot/) - [Order a server](/docs/managed-containers/order-a-server/) - [Order a service](/docs/get-started/order-a-service/) - [Take a server snapshot](/docs/managed-containers/snapshots/) ## With the API `GET /api/v1/client/servers/{uuid}/snapshots/seed` returns the restore a server ordered from a snapshot is waiting on, or had. It returns `null` for a server ordered without one. ```bash curl https://api.coritan.com/api/v1/client/servers/$SERVER/snapshots/seed \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "uuid": "9b1d3f5a-7c2e-4a6b-8d0f-1e3a5c7b9d2f", "snapshot_uuid": "5f0c9a2e-7b1d-4e8a-9c3f-2a6d8e1b4c70", "snapshot_name": "before-1.21-update", "snapshot_software_label": "Paper 1.21.4", "snapshot_size_bytes": 1932735283, "status": "completed", "error": null, "attempts": 1, "created_at": "2026-09-20T09:12:04", "started_at": "2026-09-20T09:15:40", "finished_at": "2026-09-20T09:18:02" } ``` `status` : `pending` while the server installs, `restoring` while we write the files, then `completed` or `failed`. `error` : Why the restore failed, the same reason the card shows. `attempts` : How many times the restore has run, counting each **Try again**. `snapshot_name`, `snapshot_software_label` and `snapshot_size_bytes` : The snapshot the order named. `snapshot_name` and `snapshot_size_bytes` are `null` once the snapshot is deleted. `POST /api/v1/client/servers/{uuid}/snapshots/seed/retry` runs a failed restore again and returns the record as it stands afterwards. Check its `status`: a restore that cannot start comes back `failed` at once, with the reason in `error`. Wait until the install has finished, because a restore sent while the server installs fails with `Cannot restore a snapshot while the server is installing`. ```bash curl -X POST https://api.coritan.com/api/v1/client/servers/$SERVER/snapshots/seed/retry \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` A server ordered without a snapshot answers `404` with `This server was not ordered from a snapshot`. A restore that is running or has finished answers `409` with `The seed is completed and cannot be retried`, naming its status. On a server shared with you, reading the record needs `snapshot.read` and a retry needs `snapshot.restore`. ## API - `GET /api/v1/client/servers/{uuid}/snapshots/seed`: The restore a server ordered from a snapshot is waiting on, or got (https://www.coritan.com/docs/api/reference/client/container-apps/servers-snapshots/#op-get-api-v1-client-servers-uuid-snapshots-seed) - `POST /api/v1/client/servers/{uuid}/snapshots/seed/retry`: Run a failed seed again (https://www.coritan.com/docs/api/reference/client/container-apps/servers-snapshots/#op-post-api-v1-client-servers-uuid-snapshots-seed-retry) # How snapshot storage works > How your account's snapshot allowance is worked out, what counts against it and what happens when you go over it. Source: https://www.coritan.com/docs/snapshots/storage-allowance/ In the dashboard: - /dashboard/snapshots: https://www.coritan.com/dashboard/snapshots Your account has one *snapshot allowance*: the most that all your snapshots together may take up. The snapshots of every server count against it, including servers you have deleted. The **Snapshot storage** card on the [Snapshots](https://www.coritan.com/dashboard/snapshots) page, and on each server's **Snapshots** tab, shows how much is **Used** and how much is left. ## Where the allowance comes from The allowance is the sum of what your services include: - Each server plan includes an allowance. A plan with no figure of its own includes twice its disk by default, and a free server includes room for one snapshot ([How free servers work](/docs/managed-containers/free-servers/)). The order page does not show a plan's allowance, but `breakdown` in the API's answer lists what each of your services adds ([With the API](#with-the-api)). - Each snapshot storage service adds its size ([Order more snapshot storage](/docs/snapshots/add-snapshot-storage/)). - We can add a bonus to your account. The card then says so under its title, such as `Includes a 10.0 GB bonus.` A service counts while it is active, while it is suspended and while we remove it. It stops counting when it ends. An order that waits for its first payment adds nothing yet. Other products, such as Cloud Compute instances, add nothing. The allowance belongs to the account, so it changes with your services. A move to a smaller plan lowers it, and ordering a server or storage raises it. ## What counts against it Each snapshot counts at its size once it is `Completed`, and keeps counting until it is deleted. The size is the compressed archive, which is usually smaller than the server's disk usage. A snapshot counts the same whoever took it: you, a schedule or us. A failed snapshot counts nothing, and a deleted one stops counting at once. ## Before a snapshot is taken We check that a new snapshot fits before we take it. The check uses the server's current disk usage, which is usually more than the finished archive needs. When we cannot measure the disk, the check uses the plan's full disk. The **Take snapshot…** dialog shows the estimate against what is left, and **Not enough snapshot storage** when it does not fit. The API answers `402` with a message such as `This snapshot needs 3.2 GiB and your plan includes 20 GiB, of which 18.5 GiB is already in use. Delete an older snapshot or add snapshot storage.` ([Take a server snapshot](/docs/managed-containers/snapshots/)). We check once more when the archive is ready, with its real size. If it no longer fits, because the allowance shrank or another snapshot finished first, the snapshot fails. A schedule makes room for its next snapshot by deleting the oldest snapshots it took of the same server. It never deletes a locked snapshot, or one that you, another schedule or we took ([Schedule server tasks](/docs/managed-containers/schedules/)). Snapshots we take for you before we remove a server skip the check, so that we can save the server even when the account is full. They count against the allowance once they complete ([Snapshots we take for you](/docs/snapshots/snapshots-and-backups/#snapshots-we-take-for-you)). ## When the account is over The account can go over when the allowance shrinks, or when a snapshot we take for you does not fit. The allowance shrinks when you move a server to a smaller plan, and when a server or a snapshot storage service ends. From then on: - The **Snapshot storage** card shows **Over the allowance**, with the date the cleanup starts. - New snapshots are refused until the account fits again, and so are a schedule's, unless it can make room from its own snapshots. - The cleanup date is 30 days after the account went over, by default. For an account that has no allowance left and has never paid us, it is 14 days. We email you before the cleanup date: | When | Subject | | --- | --- | | At our daily check after the account goes over | `Free up snapshot space by` the cleanup date, or `We're keeping your saved worlds until` the cleanup date when the account has no allowance left | | A week before the cleanup date | `Reminder: your oldest snapshots go on` the cleanup date | | A day before the cleanup date | The same reminder | Each email names the snapshots we would delete if nothing changes. From the cleanup date, our daily check deletes `Completed` snapshots until the rest fit, and no more. It takes the oldest first, and every unlocked snapshot before a locked one, so a lock does not keep a snapshot past the cleanup. It never deletes a snapshot that we put on hold. We then email you with the subject `2 older snapshot(s) removed`, with the number we deleted, and the names of the snapshots. > [!CAUTION] > We cannot recover a snapshot that the cleanup deleted. Download the snapshots you want to keep before the cleanup date ([Download a snapshot](/docs/snapshots/manage-snapshots/#download-a-snapshot)). As soon as the account fits again, we cancel the cleanup date. Delete snapshots you no longer need, order snapshot storage, move to a larger plan or order a server. If the account goes over again later, a new full grace period starts. To keep your snapshots for longer, [contact support](/docs/support/conversations/): we can pause the cleanup for your account, and we send no reminders while it is paused. ## With the API `GET /api/v1/client/snapshots/allowance` returns the figures the **Snapshot storage** card shows, worked out at the moment you ask. ```bash curl https://api.coritan.com/api/v1/client/snapshots/allowance \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` ```json { "allowance_bytes": 139586437120, "used_bytes": 1932735283, "available_bytes": 137653701837, "bonus_bytes": 0, "over": false, "cleanup_at": null, "over_since": null, "breakdown": [ {"service_id": 1042, "status": "active", "product": "CX4", "allowance_bytes": 85899345920}, {"service_id": 1077, "status": "active", "product": "Snapshot Storage 50 GB", "allowance_bytes": 53687091200} ] } ``` `allowance_bytes` : The whole allowance: what your services include, plus `bonus_bytes`. `used_bytes` and `available_bytes` : What your snapshots take up, and what is left. `available_bytes` is never below `0`. `over` : `true` while your snapshots take more than the allowance. `over_since` and `cleanup_at` : When the account went over, and when the cleanup starts, in UTC. Both are `null` while the account fits. `breakdown` : One entry for each service that is active, suspended or being removed, with its `service_id`, `status`, `product` name and the `allowance_bytes` it adds. A service that includes no snapshot storage adds `0`. ## API - `GET /api/v1/client/snapshots/allowance`: The account's snapshot meter (https://www.coritan.com/docs/api/reference/client/container-apps/snapshots/#op-get-api-v1-client-snapshots-allowance) # Order more snapshot storage > Add snapshot storage to your account when your servers' snapshots need more room than their plans include. Source: https://www.coritan.com/docs/snapshots/add-snapshot-storage/ In the dashboard: - /dashboard/snapshots: https://www.coritan.com/dashboard/snapshots - /dashboard/order/snapshot-storage: https://www.coritan.com/dashboard/order/snapshot-storage Snapshot storage is a service of its own that adds room to your account's snapshot allowance. It belongs to the account, so it covers the snapshots of every server you have, and it stays when a server is gone. Order it when your snapshots need more room than your server plans include, or to keep your snapshots after you cancel your last server. ## Before you begin - Each storage service adds a fixed size to the allowance, up to 2 TB. Services add up, so order more than one for more room. - For the first invoice, credit on your account pays first. Otherwise you pay by card or PayPal on the page the order lands on ([How the first payment works](/docs/get-started/order-a-service/#how-the-first-payment-works)). - [How snapshot storage works](/docs/snapshots/storage-allowance/) explains what counts against the allowance. ## Order snapshot storage Open the order page in one of these ways: - In the [dashboard](https://www.coritan.com/dashboard/snapshots), go to **Snapshots** and select **Order more storage** at the top of the page. - On a server's **Snapshots** tab, select **Order more storage** on the **Snapshot storage** card. - When you take a snapshot that does not fit, select **Order more storage** in the **Not enough snapshot storage** alert. - On [**Order a service**](https://www.coritan.com/dashboard/order), select the **Snapshot Storage** card under **Storage**. The **Snapshot storage** card at the top of the page shows how much of your allowance is **Used** and how much is left, so you can see how much room you are short of. 1. Under **Size**, choose a tier. Each one says how much it adds to the allowance and what that costs per GB a month, such as `Adds 100 GB to the snapshot allowance on this account. 2.5¢/GB a month.` 2. Choose the billing cycle with the buttons beside the **Size** heading, such as **Monthly**. A longer term names what it saves, such as `Annually · save 20%`. 3. Check the summary under **Your order**. It lists the tier with its billing cycle, with **Change** beside it, and the **Total** with what is due today. The line under the total says how the first invoice is paid. On a phone the summary follows the tiers, and **Review order** at the bottom of the screen takes you to it. 4. Select the button under the summary. It reads **Place order and pay** when you pay after placing the order, and **Place order** when your credit pays for it or your account is billed in arrears. If we refuse the order, **Could not place the order** appears above the button with the reason. You can also buy a tier with a new Container Apps server: on the server's order page, turn on the **Snapshot Storage** card under **Add-ons** and choose its **Size** ([Order a server](/docs/managed-containers/order-a-server/)). It goes on the server's first invoice and becomes a storage service of its own. ## Pay for the order Once you place the order, its own page opens. When its title is **Order placed, payment due**, pay under **Pay invoice** with account credit, a saved card or PayPal account, or a new card ([Pay and follow the order](/docs/get-started/order-a-service/#pay-and-follow-the-order)). We add the storage once the invoice is paid. To pay later, leave the page and pay the invoice under [Invoices](/docs/billing/invoices/). **What you ordered** on the order's page shows the service's number, such as `#1042`. Note it: you need it to change or cancel the storage later. ## Result We add the storage as soon as the service is set up. The order's page then reads **Your order is ready**, and its button opens the **Snapshots** page, where the **Snapshot storage** card shows the larger allowance. When the account was over its allowance and now fits, the **Over the allowance** alert goes away and we cancel the cleanup date. The service renews each billing cycle until you cancel it. The **Services** page lists it with **Snapshot Storage** in the **Product** column. ## Change or cancel snapshot storage Each size is a plan of its own, so you cannot resize a storage service. For more room, order another one. For less, cancel one. Selecting a storage service on the **Services** page opens the **Snapshots** page, which has no billing controls. To reach the service's **Billing** tab, open `https://www.coritan.com/dashboard/services//billing` in your browser, with the number from the order's page. There, **Change plan…** moves the service to another billing cycle, and **Cancel service…** cancels it ([Cancel a service](/docs/billing/cancel-a-service/)). > [!WARNING] > When a storage service ends, its room leaves the allowance. The cancel dialog says that the service's data is deleted, but ending the storage deletes no snapshot by itself. If your snapshots no longer fit, a grace period starts, and after it we delete the oldest snapshots until the rest fit ([When the account is over](/docs/snapshots/storage-allowance/#when-the-account-is-over)). If a renewal invoice goes unpaid and we suspend the service, the account keeps the room until the service ends. ## Troubleshooting `No Snapshot Storage plans yet` : Snapshot storage is not in the catalogue for your account. [Contact support](/docs/support/conversations/) if you expected it. **Could not load the plans** : The tiers did not load. Select **Try again**, or reload the page. **Could not load the snapshot allowance** : The card at the top of the page could not read your allowance. Select **Try again** on it. You can still order a tier. The button under the summary is greyed out : The line under the button says why, such as `This plan has no price yet.` Select it to go to the **Size** section, and choose another tier. The allowance has not grown : We add the storage once the service is set up, which waits for the first invoice to be paid. Pay it from [Invoices](/docs/billing/invoices/). **Over the allowance** still shows : The new storage does not cover everything your snapshots take. Delete snapshots you no longer need ([Lock, download and delete snapshots](/docs/snapshots/manage-snapshots/#delete-a-snapshot)), or order more storage. You need more than 2 TB in one service : [Contact support](/docs/support/conversations/) about a larger amount, or order several storage services. ## Related - [How snapshot storage works](/docs/snapshots/storage-allowance/) - [Snapshots](/docs/snapshots/) - [Order a service](/docs/get-started/order-a-service/) - [Pay an invoice](/docs/billing/invoices/) - [Cancel a service](/docs/billing/cancel-a-service/) ## With the API Snapshot storage tiers are products whose `module_name` is `storage` in [`GET /products/`](/docs/api/reference/client/catalog/#op-get-api-v1-products). Order one with [`POST /services/order`](/docs/api/reference/client/services/#op-post-api-v1-services-order), as [Order a service](/docs/get-started/order-a-service/#with-the-api) describes. The size comes from the tier, so `config` can be empty. If you send `storage_gb`, it must match the tier's size. ```bash curl -X POST https://api.coritan.com/api/v1/services/order \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "product_id": 41, "pricing_id": 118, "idempotency_key": "8d2f6a14-3c7e-4b9a-a1d5-6e0c9f2b7a38", "config": {"storage_gb": 100} }' ``` A `storage_gb` that does not match the tier answers `422` with `{"detail": {"errors": [...]}}`, such as `This plan is 50 GB of snapshot storage; to buy 100 GB choose the plan for that size`. A `storage_gb` that is not a whole number answers `storage_gb must be an integer`. To buy a tier with a new server, put it in the server order's `addons`, as `{"product_id": 41}`. We take its price on the server's billing cycle, else its monthly price. A server order takes one Snapshot Storage add-on, and a `storage_gb` that does not match answers the same `422`, with the tier's name in front ([Add-ons in the order](/docs/get-started/order-a-service/#add-ons-in-the-order)). `GET /api/v1/services/` lists the storage service with its `id`. Cancel it with `POST /api/v1/services/{service_ref}/cancel` ([Cancel a service](/docs/billing/cancel-a-service/#with-the-api)). `GET /api/v1/client/snapshots/allowance` shows the allowance it adds ([How snapshot storage works](/docs/snapshots/storage-allowance/#with-the-api)). ## API - `POST /api/v1/services/order`: Order a platform service, and any add-ons bought with it (https://www.coritan.com/docs/api/reference/client/services/#op-post-api-v1-services-order) # Snapshots and backups compared > How a snapshot differs from a server backup, where each is kept and which one to use. Source: https://www.coritan.com/docs/snapshots/snapshots-and-backups/ A snapshot and a backup both hold a copy of a server's files. A backup belongs to the server it came from, and it goes when that server goes. A snapshot belongs to your account, so it stays after you delete the server, and you can restore it onto any of your servers. Snapshots have replaced backups: you can take new snapshots, and you can only use the backups a server already has. ## How they differ | Detail | Snapshot | Backup | | --- | --- | --- | | Belongs to | Your account | The server it came from | | When you delete the server | Stays on your account | Deleted with the server | | Restores onto | Any of your servers that runs the same game | Only the server it came from | | Before a restore | Deletes the server's files, unless you clear **Wipe the server first** | Always deletes the server's files | | Start a new server from it | Yes, [through the API](/docs/snapshots/new-server-from-a-snapshot/) | No | | New copies | Taken by you, by a schedule, or by us before we remove a server | No longer possible | | Where you find it | The **Snapshots** page and each server's **Snapshots** tab | The server's **Backups** tab | | Counts against | Your account's [snapshot allowance](/docs/snapshots/storage-allowance/) | Nothing | | Download link | Works for 6 hours by default | Works for 10 minutes | | On a free server | Room for one snapshot, and no download | None | | A lock | Stops deletion, apart from the storage cleanup | Stops deletion | Neither one holds the server's [databases](/docs/managed-containers/databases/). On both, a lock stops anyone deleting the copy until someone unlocks it. On a snapshot, it also stops a schedule deleting the snapshot to make room. A lock does not keep a snapshot past the storage cleanup, which deletes locked snapshots after the unlocked ones when the account stays over its allowance ([How snapshot storage works](/docs/snapshots/storage-allowance/#when-the-account-is-over)). ## Which one to use Take a snapshot whenever you want to save a server, such as before an update or before you delete the server ([Take a server snapshot](/docs/managed-containers/snapshots/)). You cannot take a new backup. The backups a server already has stay on its **Backups** tab until you delete them or the server is deleted. Restore a backup only to go back to a point that no snapshot covers ([Manage server backups](/docs/managed-containers/backups/)). Before you delete a server, download the backups you want to keep, because they are deleted with it ([Download a backup](/docs/managed-containers/backups/#download-a-backup)). ## Snapshots we take for you When we are about to remove a server, we take a snapshot of it first, so that you can get the server back. The **Snapshots** page marks these snapshots **By the platform**. The name says why we took the snapshot and ends with the date in UTC, such as `Saved before cancellation · 2026-09-16`. `Saved before cancellation` : You cancelled the server and left **Take a snapshot first** ticked, which is the default. We take the snapshot at the moment you cancel. After a cancellation at the end of the term, it holds the server as it was on the day you cancelled ([Cancel a service](/docs/billing/cancel-a-service/)). `Saved before removal for non-payment` : An invoice for the server is unpaid, and we have set a date to delete the server. By default, we set that date 3 days after we suspend the server ([Failed payments and suspended services](/docs/billing/failed-payments/#an-email-says-the-service-will-be-deleted)). `Saved before inactivity cleanup` : Nobody has joined or started a free server for 14 days, by default, and we are about to suspend it ([How free servers work](/docs/managed-containers/free-servers/#free-servers-nobody-uses)). We skip the snapshot when the server already has one from the last 24 hours that is complete or still being taken, whoever took it. When the snapshot cannot start, the cancellation, suspension or removal goes ahead without it. These snapshots skip the allowance check, so we take them even when your account has no room left. Once they complete, they count against the allowance like any other snapshot. When we delete a server, we first wait up to 2 hours, by default, for any of its snapshots that is still being taken. ### When a suspension ends When we lift a suspension, for example after you pay the overdue invoice, the server is back. We then delete every snapshot we took of it, including one saved when you cancelled it. We keep a snapshot that you locked, or that we put on hold. To keep one of these snapshots, lock it before the suspension ends ([Lock or unlock a snapshot](/docs/snapshots/manage-snapshots/#lock-or-unlock-a-snapshot)). ### When the server is gone A snapshot we took stays on your account after we delete the server. You can restore it onto another server ([Restore a snapshot onto a server](/docs/snapshots/restore-a-snapshot/)), or [start a new server from it](/docs/snapshots/new-server-from-a-snapshot/). When the server's service ends, the snapshot allowance its plan included ends too. If your snapshots then take more than the allowance, we keep them until the cleanup date, and then delete the oldest until the rest fit. The cleanup date is 30 days after the account goes over, by default, or 14 days for an account that has no allowance left and has never paid us ([When the account is over](/docs/snapshots/storage-allowance/#when-the-account-is-over)). To keep a snapshot past that date, download it, or add room by ordering [snapshot storage](/docs/snapshots/add-snapshot-storage/) or a server. # Organizations > Resell Coritan products under your own brand, with your own customers, products, prices, storefront and staff. Source: https://www.coritan.com/docs/organizations/ In the dashboard: - /dashboard/organizations: https://www.coritan.com/dashboard/organizations An *organization* is a reseller brand you run on Coritan. It has its own customers, products, prices, storefront, staff and payouts, while Coritan runs the servers, instances, mail and DNS underneath. Use it when you want to sell hosting under your own name instead of sending people to coritan.com. Every organization you own or work in is listed under **Organizations** in the [dashboard](https://www.coritan.com/dashboard/organizations), with your role, its tier, its status and its billing mode. Select one to open its tabs. An organization its owner has deleted leaves the list for everyone. ## Set up an organization - [How organizations work](/docs/organizations/how-organizations-work/): the parts of an organization, the three kinds of account and which credential each API takes. - [Create an organization](/docs/organizations/create-an-organization/) - [Change organization settings](/docs/organizations/settings/): name, billing mode, branding, legal details, invoicing and outgoing email. - [Manage members and roles](/docs/organizations/members-and-roles/) and the [roles and permissions](/docs/organizations/roles-and-permissions/) each role carries. - [Create organization API keys](/docs/organizations/api-keys/) - [Delete an organization](/docs/organizations/delete-an-organization/) ## Sell to your customers - [Set up products and pricing](/docs/organizations/products-and-pricing/) - [Manage customers](/docs/organizations/customers/) - [Manage customer services](/docs/organizations/customer-services/) - [Read your organization's overview](/docs/organizations/overview/): revenue, customers and storefront visitors. - [Run your store from the Commerce tab](/docs/organizations/commerce/): orders, products, stock and shipping for goods that Coritan sells on your behalf. ## Get paid - [Organization billing](/docs/organizations/billing/): how customers pay and how Coritan pays you. - [Manage customer invoices](/docs/organizations/billing/invoices/), [generate statements](/docs/organizations/billing/statements/), [track your payouts](/docs/organizations/billing/payouts/) and [follow payment disputes](/docs/organizations/billing/disputes/). - [Connect your PayNow store](/docs/organizations/billing/paynow/) - [Track your store's balance and payouts](/docs/organizations/commerce/payouts/) and [respond to chargebacks on store orders](/docs/organizations/commerce/disputes/). ## Run the products your customers use - [Manage your customers' instances](/docs/organizations/compute/) on Cloud Compute. - [Host DNS zones for your organization](/docs/organizations/dns/) - [Run apps for an organization](/docs/apps/organization-apps/) from its **Apps** tab. - [Run Mail Hosting for your customers](/docs/organizations/mail/) - [Organization integrations](/docs/organizations/integrations/): Discord and the game proxy. - [Receive organization webhooks](/docs/organizations/webhooks/) - [Read the organization audit log](/docs/organizations/audit-log/) ## Build your storefront and staff console - [Build a storefront on the Organization API](/docs/organizations/storefront/): the public catalogue, customer sign-in, the customer portal, the community, the Commerce API and the Store API. - [The staff console](/docs/organizations/staff-console/): where your team looks after customers, servers, billing and support. ## How it is billed Customers pay your organization for the products you define, at your prices, and Coritan pays you what it collected less the platform fee. The products you link to Coritan's own run on your Coritan account at Coritan's prices. [Organization billing](/docs/organizations/billing/) explains both sides. ## Limits Coritan sets each organization's limits: how many customers, customer services and products it can hold. The API refuses the next one with `403` and a message such as `Customer limit reached (100/100)`. Contact [support](https://www.coritan.com/dashboard/support) to raise a limit. The Organization API only answers for an organization whose status is `active`. For a suspended, closed or deleted organization, every member call answers `404 Organization not found`. ## API - `GET /api/v1/orgs`: List organizations (https://www.coritan.com/docs/api/reference/organizations/organizations-members/orgs/#op-get-api-v1-orgs) # How organizations work > The parts of an organization, who can reach each one, how money moves and which credential each API takes. Source: https://www.coritan.com/docs/organizations/how-organizations-work/ An organization lets you sell Coritan products as your own. You decide what to sell and at what price, your customers sign in on your storefront, and your team looks after them. Coritan runs the resources, takes the payments as merchant of record and pays you out. ## The parts of an organization Owner : The Coritan account that created the organization. The owner has every permission, is the only person who can delete the organization, and cannot be removed or demoted. The resources behind linked products run on the owner's Coritan account. Members : Other Coritan accounts you add, each with a role. Members work in the organization's tabs in the Coritan dashboard and in the [staff console](/docs/organizations/staff-console/). [Organization roles and permissions](/docs/organizations/roles-and-permissions/) lists what each role can do. Customers : The people who buy from you. A customer account belongs to your organization only: it is not a Coritan account, and a customer never sees coritan.com. Customers sign in on your storefront with an email or a username and a password. Products and pricing : What your customers can buy. A *linked* product is backed by a Coritan product, such as a Container Apps plan, and ordering it creates the real resource. A *standalone* product is something you deliver yourself, and nothing is created on Coritan. Each product has one price per billing cycle. See [Set up products and pricing](/docs/organizations/products-and-pricing/). Customer services : What a customer holds after an order: one product at one price, renewed each billing cycle until it is cancelled. See [Manage customer services](/docs/organizations/customer-services/). Storefront : The site where customers browse, order, pay and manage what they bought. You build it on the [storefront APIs](/docs/organizations/storefront/), and set its logo, colours and legal links in [Settings](/docs/organizations/settings/). ## How money moves Your organization's *billing mode* decides who takes your customers' payments. | Billing mode | Who takes payment | Invoices and payouts | | --- | --- | --- | | `platform_mor` (Platform merchant of record) | Coritan's payment gateways, with Coritan as merchant of record. | Coritan invoices your customers when their services come due and pays you each month. | | `own_gateway` (Own gateway) | Your own [PayNow store](/docs/organizations/billing/paynow/). Checkout fails until it is connected. | Coritan raises no renewal invoices and makes no payouts. The money reaches you through PayNow. | | `hybrid` (Hybrid) | Your PayNow store when it is connected, otherwise Coritan's gateways. | Coritan invoices and pays you out for the money its gateways took. | A payout is what Coritan's gateways collected for you, less refunds and chargebacks, less the *platform fee*: a percentage Coritan sets for each organization and shows in the organization's header. Invoices you mark paid, invoices paid from customer credit and money your own PayNow store took are not part of a payout. [Track your payouts](/docs/organizations/billing/payouts/) explains the monthly cycle. Separately, a linked product's resource runs on the owner's Coritan account, which is billed Coritan's price for it like any other Coritan service. Your margin is the difference between your price and Coritan's. See [billing](/docs/billing/) for how a Coritan account pays. ## Credentials for each API Every Organization API path starts with `https://api.coritan.com/api/v1/orgs/{org_slug}`, where `{org_slug}` is your organization's slug. Which credential a call takes depends on who is calling. | Caller | Paths | Credential | | --- | --- | --- | | A member, from your own tools | The organization itself, members, customers, products, services, billing, DNS, mail, webhooks | `Authorization: Bearer` with the member's Coritan access token, or a staff console session | | Staff, in the staff console | `/staff/...` and `/chat/...` | `Authorization: Bearer` with a staff session from [staff sign-in](/docs/organizations/staff-console/sign-in/) | | A signed-in customer | `/portal/...` and the customer's own `/auth/...` calls | `Authorization: Bearer` with a customer token from [customer sign-in](/docs/organizations/storefront/customer-sign-in/) | | Anyone | `/storefront/...` | None | | Your servers | `/commerce/...` | `X-API-Key` with an [organization API key](/docs/organizations/api-keys/) and the scopes it needs, or a member's token | | Your checkout page | `/store/...` | `x-publishable-api-key` with a publishable key, plus the shopper's token for their own orders | An organization API key works on the Commerce API only. Every other member call needs a person's token, so an automation that manages customers or products signs in as a member. ## Limits Coritan sets each organization's tier, platform fee and limits on customers, customer services and products. The dashboard shows the tier in the list of organizations. When a limit is reached, the API refuses the next customer, service or product with `403` and names the count, for example `Service limit reached (500/500)`. ## Related - [Create an organization](/docs/organizations/create-an-organization/) - [Organization billing](/docs/organizations/billing/) - [API authentication](/docs/api/) # Create an organization > Create an organization from your Coritan account, choose its slug and billing mode, and open its dashboard. Source: https://www.coritan.com/docs/organizations/create-an-organization/ Create an organization when you want to sell Coritan products under your own brand. The account that creates it becomes its owner, and the organization opens in the dashboard ready for products, customers and members. ## Before you begin - Sign in to the Coritan account that should own the organization. The resources behind the products you link to Coritan's run on this account, and only this account can delete the organization later. - Choose a slug. It appears in every Organization API path and cannot be changed after the organization is created. - Decide how your customers will pay. [How organizations work](/docs/organizations/how-organizations-work/#how-money-moves) compares the three billing modes, and you can change the mode later in [Settings](/docs/organizations/settings/). ## Create it in the dashboard 1. Go to **Organizations** in the [dashboard](https://www.coritan.com/dashboard/organizations) and select **New organization**. 2. Enter a **Name** of 2–255 characters. Customers see it on your storefront. 3. Check the **Slug**. The dashboard fills it in from the name until you edit it. It takes 2–100 lowercase letters, numbers and hyphens, and starts and ends with a letter or a number, such as `acme`. 4. Choose a **Billing mode**: **Platform merchant of record**, **Own gateway** or **Hybrid**. 5. Select **Create organization**. ## Result The dashboard confirms that the organization was created and opens its **Overview** tab. You are its owner. The organization is `active`, and Coritan has set its tier, platform fee and limits, which the list of organizations and the organization's header show. Next, set it up: 1. Add your name, logo, colours, storefront domain and support details in [Settings](/docs/organizations/settings/). 2. [Set up products and pricing](/docs/organizations/products-and-pricing/). 3. [Invite members](/docs/organizations/members-and-roles/) to help you run it. ## Troubleshooting `Slug already taken` : Another organization uses that slug. Choose a different one. `Enter a slug of at least two characters: lowercase letters, digits and hyphens.` : The slug is shorter than two characters once the dashboard has removed what a slug cannot hold. Type a longer one. `422` from the API : The slug breaks the pattern, for example by ending in a hyphen, or the name is shorter than two characters. ## Related - [How organizations work](/docs/organizations/how-organizations-work/) - [Change organization settings](/docs/organizations/settings/) - [Delete an organization](/docs/organizations/delete-an-organization/) ## With the API Create the organization with your Coritan access token. The body takes `name`, `slug` and, optionally, `billing_mode` (`platform_mor` when you leave it out, or `own_gateway` or `hybrid`). ```bash curl -X POST https://api.coritan.com/api/v1/orgs \ -H "Authorization: Bearer $CORITAN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "Acme Hosting", "slug": "acme", "billing_mode": "platform_mor"}' ``` The answer is `201` with the organization: ```json { "id": 42, "slug": "acme", "name": "Acme Hosting", "tier": "starter", "status": "active", "billing_mode": "platform_mor", "platform_fee_percent": "10.00", "max_customers": 100, "max_services": 500, "max_custom_products": 50, "my_role": "owner" } ``` The values of `tier`, `platform_fee_percent` and the `max_` limits are the ones Coritan set for your organization. Read the organization again at any time with `GET /api/v1/orgs/acme`, and list every organization you belong to with `GET /api/v1/orgs`, newest first, each with your role in `my_role`. ## API - `POST /api/v1/orgs`: Create organization (https://www.coritan.com/docs/api/reference/organizations/organizations-members/orgs/#op-post-api-v1-orgs) - `GET /api/v1/orgs/{org_slug}`: Get organization (https://www.coritan.com/docs/api/reference/organizations/organizations-members/#op-get-api-v1-orgs-org-slug) # Read your organization's overview > See revenue, customers, services and storefront visitors for an organization, and add the visitor tracking script to your site. Source: https://www.coritan.com/docs/organizations/overview/ In the dashboard: - /dashboard/organizations/…/overview: https://www.coritan.com/dashboard/organizations The **Overview** tab opens when you select an organization. It shows what your customers have paid and still owe, who is on your storefront today and right now, what your plan allows and what changed recently. ## Before you begin Every member can open the tab. The revenue figures need the owner, admin or billing role, and **Recent activity** needs the owner or admin role. Other members see links to the other tabs in its place. ## Read the revenue figures **Revenue collected** : What your customers paid through the payment gateways, Coritan's and your own PayNow store's, plus the money they added to their account credit, less refunds and chargebacks. It counts from the day the organization began. Invoices marked paid and invoices paid from credit you granted are not in it, because nobody was charged. **Outstanding** : The total of your unpaid and overdue invoices. When customers owe money in more than one currency, the other currencies follow under the figure. **Active customers** and **Active services** : How many of each are active, against the number your plan allows. For a period's figures with the gateway fees taken off, [generate a statement](/docs/organizations/billing/statements/). ## Read the storefront traffic **Storefront today** counts the visits to your storefront since midnight UTC: **Sessions**, **Unique visitors**, **Page views**, visits by customers who were **Signed in**, the **Average visit** length and the **Bounce rate**. Select the refresh button in the card to update it. **Live now** shows the visitors on your storefront this minute, split into **Signed in** and **Guests**, and the **Busiest pages** they are on. When your storefront runs on Coritan, its visits count automatically. To count the visits to another site of yours, add the tracking script to its pages (see [With the API](#with-the-api)). ## Read your plan limits **Plan limits** lists how many **Customers**, **Services**, **Custom products** and **Sub-resellers** your plan allows, and its **API calls per month**. Coritan sets the plan. Contact [support](https://www.coritan.com/dashboard/support) to change it. ## Troubleshooting "Only owners, admins and billing members see revenue figures." : Your role does not include billing. Ask the owner or an admin for the billing role if you need the figures. **Revenue collected** is lower than your paid invoices : Invoices marked paid, and invoices paid from credit you granted, do not count as revenue. The traffic cards stay at zero : No visitor has reached a page that carries the tracking script. A storefront on your own server needs the script added. ## Related - [Generate organization statements](/docs/organizations/billing/statements/) - [Read the organization audit log](/docs/organizations/audit-log/) - [How organizations work](/docs/organizations/how-organizations-work/) ## With the API The revenue figures take the owner, admin or billing role; the traffic routes take any member. ```bash curl "https://api.coritan.com/api/v1/orgs/acme/stats/revenue" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` The answer has `total_revenue`, the parts it is made of (`gateway_payments`, `wallet_topups`, `refunded`, `charged_back` and `own_store_collected`), the paid invoice value it leaves out (`settled_with_credit` and `marked_paid`), `outstanding_by_currency` (one `currency` and `amount` pair for each currency still owed), `active_customers` and `active_services`. Get the tracking script for a site that does not run on Coritan: ```bash curl "https://api.coritan.com/api/v1/orgs/acme/tracking/script" \ -H "Authorization: Bearer $CORITAN_TOKEN" ``` The answer's `snippet` is a `