Skip to content
Coritan Docs

Let customers manage IPs, protection and domains

The portal API for a customer's Floating IPs, DDoS Shield, external servers and platform domain names.

View as Markdown

These portal routes let a customer order and attach Floating IPs, filter the traffic that reaches them with DDoS Shield, point an external server at a game server they host elsewhere, and claim a free name under a domain your brand lends. Every route takes the customer's token as Authorization: Bearer $CUSTOMER_TOKEN and works only on services that customer bought. Sharing a server with another customer does not share its addresses.

  • Get a customer token as Sign customers in to your storefront describes.
  • Sell the plans in your catalogue: an IP product for addresses and an external server product for external servers. Set up products and pricing explains products and plans.
  • Use the right id. The IP and DDoS Shield routes take the address's service_id from GET /portal/ips. The external server routes take the service id from GET /portal/external-servers, which is the id GET /portal/services returns too.
  1. List the pools that sell what the customer wants:

    Shell
    curl "https://api.coritan.com/api/v1/orgs/acme/portal/ips/pools?version=ipv4&prefix_len=32" \
      -H "Authorization: Bearer $CUSTOMER_TOKEN"
    

    Each pool in pools has its id, name, region, country_code and geo_label, and available says whether it has an address to sell. version is ipv4 or ipv6. prefix_len is 32 for one address, or 24–29 for a subnet; the list is empty for subnets while we do not rent them. region narrows the list to one location. IP pools and regions describes the pools.

  2. Find the id of your IP product and of its plan, as List products and plans shows.

  3. Create a key for this checkout in the browser, such as the value of crypto.randomUUID(), and place the order:

    Shell
    curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/ips/order" \
      -H "Authorization: Bearer $CUSTOMER_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"org_product_id": 14, "org_pricing_id": 40, "pool_id": 3, "prefix_len": 32, "version": "ipv4", "idempotency_key": "5b0f7c1e-ip-order"}'
    

    | Field | What it holds | | --- | --- | | org_product_id, org_pricing_id | Your IP product and the plan. | | pool_id | The pool from step 1. The address comes from this pool only. | | prefix_len, version | The size and family. They default to 32 and ipv4. | | idempotency_key | Required. 1–64 letters, digits, dots, underscores, colons or hyphens. Keep it for the whole checkout, so a second click or a second tab returns the same order. | | attach_to_org_service_id | Optional. The service id of one of the customer's Container Apps servers that has no floating IP yet. We attach the address to it as soon as it is assigned. It works only when prefix_len is 32. | | hostname | Optional. A name for the service. |

    The answer has the fields of a storefront order, such as invoice_id, requires_payment and status. It adds intent, which follows the order, and hold_expires_at.

  4. When requires_payment is true, send the customer to pay, as Pay an invoice shows. We keep the address for the customer until hold_expires_at. An order that stays unpaid past then is cancelled, and the address goes back to the pool.

Route What it does
GET /portal/ips/orders The customer's open orders, so a checkout carries on after a reload or in another tab. Each has its idempotency_key and a status of started, held, invoiced or provisioning.
GET /portal/ips/orders/{idempotency_key} One order, with its invoice's invoice_number, total, currency, due_date and invoice_status.
POST /portal/ips/orders/{idempotency_key}/cancel Cancels an order whose address is not assigned yet, with its open invoice.

Sending an order again with the same key returns the first order, with replay: true, once it has an invoice. A key whose order failed, was cancelled or expired starts a new order.

GET /portal/ips returns items and total. Each item is one address or one subnet:

Field Meaning
service_id The id the other IP and DDoS Shield routes take.
kind standalone for one address, or block for a subnet.
address, cidr, version The address, or the subnet's first address, with its prefix.
service_status The service's status, such as active or suspended.
pool_name, region, geo_label Where the address comes from.
target_type, target_service_id, is_primary, attached_at What the address is attached to. They are empty when it is not attached.
host_state, host_error On a Container Apps server, whether the address has reached the server: pending, delivered, failed or removing, and why it failed.
ptr_hostname The reverse DNS name of a single address.

Show prices and due dates from the customer's service in GET /portal/services, which holds what the customer pays you.

GET /portal/ips/{service_id} returns one item. A subnet also has hosts_total, hosts_attached and hosts_available. GET /portal/ips/{service_id}/hosts lists each address in it, with attachable and what it is attached to; add include_reserved=false to leave out the addresses nobody can attach. How subnets work explains which addresses those are.

  1. List what the address can go on:

    Shell
    curl "https://api.coritan.com/api/v1/orgs/acme/portal/ips/attach-targets" \
      -H "Authorization: Bearer $CUSTOMER_TOKEN"
    

    Each item is one of the customer's Cloud Compute instances (module_name is vps) or Container Apps servers (container), with its service_id, hostname and has_floating_ip.

  2. Attach the address:

    Shell
    curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/ips/$IP_SERVICE_ID/attach" \
      -H "Authorization: Bearer $CUSTOMER_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"target_type": "vps", "target_service_id": 5120}'
    

    target_type is vps or container, and target_service_id is the target's service_id from step 1. For a subnet, host_address picks the address in it; leave it out to use the first free one. On an instance, the first address you attach becomes the primary one; send is_primary: true to make a later address primary, or false to keep it secondary. A server takes one floating IP.

  3. To take it off, send POST /portal/ips/{service_id}/detach. For a subnet with more than one address attached, send the one to take off as host_address.

Attach and detach a floating IP explains what attaching does on each kind of target.

Shell
curl -X PATCH "https://api.coritan.com/api/v1/orgs/acme/portal/ips/$IP_SERVICE_ID/ptr" \
  -H "Authorization: Bearer $CUSTOMER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"hostname": "mail.example.com"}'

hostname must be a full domain name, up to 253 characters. For an address in a subnet, add the address as host_address. GET /portal/ips/{service_id}/ptr reads the current name, and hostname is null when there is none. Set reverse DNS for a floating IP covers reverse DNS in more detail.

Filter traffic with DDoS Shield

Section titled Filter traffic with DDoS Shield

DDoS Shield filters traffic to every address with our standard profile. A customer can give an address a profile of their own and add rules to it.

Route What it does
GET /portal/shield/status Each of the customer's addresses in ips, with its ip_service_id, address, whether it is attached, and its protection: mode is platform_default or custom, with the profile's profile_name.
GET /portal/shield/events Attacks on the customer's addresses, with the ones still running first. Narrow them with ip_service_id and limit (default 50).
GET /portal/shield/subjects/{ip_service_id}/profile The address's mode, profile and rules. editable is false on the standard profile, which the customer can read and cannot change.
PATCH /portal/shield/subjects/{ip_service_id}/profile Gives the address a profile of the customer's own and returns it. Sending it again changes nothing.
POST /portal/shield/subjects/{ip_service_id}/rules Adds a rule, and gives the address its own profile first when it has none.
DELETE /portal/shield/subjects/{ip_service_id}/rules/{rule_id} Deletes a rule.

Each event has the address, the attack_type, the drop_reason, its size as pps, bps and unique_sources, the top_sources, started_at and ended_at, and active while it is still running. Read attack events explains what they mean.

A profile of the customer's own starts with our standard rate limits and no rules, and lets through traffic that no rule matches. Add a rule:

Shell
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/shield/subjects/$IP_SERVICE_ID/rules" \
  -H "Authorization: Bearer $CUSTOMER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Block a scanner", "action": "deny", "src_prefix": "203.0.113.0/24"}'
Field What it matches or does
action deny (the default), allow or rate_limit.
rate_limit_pps The packets per second a rate_limit rule lets through.
protocol The IP protocol number, such as 6 for TCP, 17 for UDP or 1 for ICMP.
src_prefix, dst_prefix A source or destination range in CIDR form, such as 203.0.113.0/24.
src_port_min, src_port_max, dst_port_min, dst_port_max Port ranges. A minimum with no maximum matches that one port.
tcp_flags_mask, tcp_flags_value The TCP flags to test, and the value they must have.
packet_len_min, packet_len_max, ttl_min, ttl_max Ranges of packet length and time to live.
icmp_type, icmp_code The ICMP type and code.
name, notes, enabled A label up to 191 characters, a note, and false to keep the rule without applying it.

Check each value in your storefront before you send it: protocol, TCP flag, time to live and ICMP values are whole numbers from 0 to 255, ports and packet lengths from 0 to 65535, and rate_limit_pps is a positive number.

Warning

A field you leave out matches every packet, and action defaults to deny. A rule sent with no match fields blocks all traffic to the address.

Change a floating IP's DDoS protection describes profiles and rules for Coritan customers.

An external server gives a game server the customer hosts elsewhere a join address on your network. The customer orders it through Take an order, and these routes manage it once it exists. How external servers work explains the product.

GET /portal/external-servers lists the customer's external servers. Each has the id the routes take, name, billing_status, join_address, mode, upstream (the host and port players are sent to), region, and the last test in probe. state is online, offline, suspended or unknown. Until its first invoice is paid, an external server has no join address and its state is unknown. GET /portal/external-servers/{id} adds edge_addresses and custom_domain, and GET /portal/external-servers/regions lists the regions it can move to, with the default_region_id.

Change where players go:

Shell
curl -X PATCH "https://api.coritan.com/api/v1/orgs/acme/portal/external-servers/$SERVICE_ID" \
  -H "Authorization: Bearer $CUSTOMER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"upstream_host": "203.0.113.25", "upstream_port": 25565}'
Field What it changes
upstream_host, upstream_port Where the server listens: a public IP address or a full hostname, and a port from 1 to 65535.
mode minecraft_java or minecraft_bedrock.
proxy_protocol true sends each player's address in a PROXY protocol header. Turn it on only when the server expects one. Bedrock servers cannot use it.
real_ip With PROXY protocol off, adds the player's address to the hostname in the handshake. It is on by default. Turn it off for a proxy such as Velocity or BungeeCord that cannot accept the longer hostname.
bedrock_bridge For a Java server that also runs Geyser, lets Bedrock players join at the same address.
location_id The region players connect through, from the regions list.
display_name The server's name, 1–100 characters.

We refuse an address in a private or reserved range, an address of our own proxies, another customer's server on our network, and a server already connected to another external server. The answer is the external server as it now stands. After an address change we test the server at once, so probe shows whether it answers. POST /portal/external-servers/{id}/test tests it again and returns the probe with a state of online or offline. Connect a server you host elsewhere covers the connection itself.

Change what players see in their server list with PATCH /portal/services/{service_id}/gameproxy, as Show the customer's services describes, and list the server in your directory with PUT /portal/services/{service_id}/community-listing, as Run a community with a server list, forum and guides describes.

Section titled Link a domain to an external server

The routes under /portal/external-servers/{id}/custom-domain work as Link a custom domain describes for a Container Apps server. GET shows the link and the expected_cname_target, POST links a domain and subdomain, POST /verify checks the record now, PATCH changes include_in_list, and DELETE unlinks the name.

When the name is under a free name the customer claimed, as the next section describes, we write the CNAME record for them and the link is verified at once. Use a subdomain such as play there, since a CNAME record cannot sit at the root of a zone we host.

Offer free names under your domain

Section titled Offer free names under your domain

When your brand lends a domain, such as example.com, each customer can claim a name under it, such as survival.example.com, and use it for their servers. We set up the domains your brand lends; ask support to add one. The names resolve only when that domain's zone is on Coritan's nameservers, since we delegate each name from it.

  1. Read the overview:

    Shell
    curl "https://api.coritan.com/api/v1/orgs/acme/portal/platform-domains" \
      -H "Authorization: Bearer $CUSTOMER_TOKEN"
    

    suffixes lists the domains on offer, and claims the customer's names. limit is how many names a customer may hold, and remaining how many more they can claim; remaining is null when there is no limit. rules has the min_length, max_length and pattern a name must fit, and the reserved names nobody can claim. Once the customer has a name, suggestions proposes a hostname under it for each product, such as play.survival.example.com for a game server.

  2. Check a name while the customer types it:

    Shell
    curl "https://api.coritan.com/api/v1/orgs/acme/portal/platform-domains/availability?label=survival&suffix=example.com" \
      -H "Authorization: Bearer $CUSTOMER_TOKEN"
    

    The answer has available, the full name as fqdn, and the reason when it is not available. suffix defaults to the first domain on offer.

  3. Claim it:

    Shell
    curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/platform-domains/claims" \
      -H "Authorization: Bearer $CUSTOMER_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"label": "survival", "suffix": "example.com"}'
    

    The answer, with status 201, has the fqdn and zone_id. parent_delegated says whether we delegated the name from your domain's zone, dnssec whether we signed it, and certificate whether we requested a wildcard certificate for it, with the reason when we did not.

Each name is a DNS zone that we sign with DNSSEC. It appears on your organization's DNS tab, where your team can add and change its records, as Host DNS zones for your organization describes. The portal has no routes for customers to edit records.

DELETE /portal/platform-domains/claims/{claim_id}, with the claim's id from claims, releases a name and returns its fqdn.

Warning

Releasing a name deletes its zone with every record in it, and we stop renewing its certificates. Hostnames under it stop resolving, so move anything that uses them first.

Your storefront lets each customer order, attach and protect their addresses, run their external servers and claim names, and every route checks that the service belongs to the customer who sent it. Claims, releases and changes to external servers appear in your organization audit log with the customer as the actor.

Unsuspend the IP service before attaching
The address's service is suspended. Unsuspend it as Suspend or unsuspend a service describes.
Container already has a primary floating IP; detach first
A server takes one floating IP. Detach the other address first.
Subnet has multiple attached hosts; pass host_address to detach one
Send the address to take off as host_address.
host_address / host_inventory_id only apply to subnet block services
The address is a single one. Leave out host_address.
No free hosts available in this subnet
Every address in the subnet is attached. Detach one first.
An identical IP order is already in progress; retry in a moment
The first request with this key is still running. Retry with the same key.
This IP order can no longer be cancelled
The order is past payment, or has already ended. To give up an address the customer holds, cancel its service as Cancel a service shows.
Only a single IPv4 can be attached to a server from its Network tab
attach_to_org_service_id works only when prefix_len is 32. Order a subnet without it.
PTR hostname must be a fully-qualified domain name
Send a full name, such as mail.example.com.
403 with Rule does not belong to this subject profile
The rule is on another address's profile. Delete it through that address.
409 with This external server is not connected yet; pay its invoice first
The external server has no join address until its first invoice is paid.
409 with This external server is suspended
A suspended external server cannot be changed. Unsuspend its service first.
422 with errors
We refused the new address or setting, and errors says why, such as an address in a private range or PROXY protocol on a Bedrock server.
A CNAME can't sit at the root of a zone hosted here. Use a subdomain such as play.
The domain is one of the customer's free names. Link a subdomain of it.
404 with This brand does not offer platform names
Your brand lends no domain yet. Ask support to add one.
409 with Already taken
Another customer holds the name, or a game server's join address uses it. Choose another.
429 with rate_limited
The customer repeated an action too quickly. Wait, then try again.

API operations on this page

MethodPathWhat it does
GET/api/v1/orgs/{org_slug}/portal/ipsList my IPs
GET/api/v1/orgs/{org_slug}/portal/ips/attach-targetsList attach targets
POST/api/v1/orgs/{org_slug}/portal/ips/orderOrder a floating IP / subnet via the org storefront path
GET/api/v1/orgs/{org_slug}/portal/ips/ordersOpen IP order intents, so unpaid or in-flight orders resume after login or in a new tab
GET/api/v1/orgs/{org_slug}/portal/ips/orders/{idempotency_key}Get my IP order
POST/api/v1/orgs/{org_slug}/portal/ips/orders/{idempotency_key}/cancelCancel my IP order
GET/api/v1/orgs/{org_slug}/portal/ips/poolsList sellable pools
GET/api/v1/orgs/{org_slug}/portal/ips/{service_id}Get my IP
POST/api/v1/orgs/{org_slug}/portal/ips/{service_id}/attachAttach IP
POST/api/v1/orgs/{org_slug}/portal/ips/{service_id}/detachDetach IP
GET/api/v1/orgs/{org_slug}/portal/ips/{service_id}/hostsList my IP hosts
GET/api/v1/orgs/{org_slug}/portal/ips/{service_id}/ptrGet PTR
PATCH/api/v1/orgs/{org_slug}/portal/ips/{service_id}/ptrSet PTR
GET/api/v1/orgs/{org_slug}/portal/shield/eventsShield events list
GET/api/v1/orgs/{org_slug}/portal/shield/statusShield status
GET/api/v1/orgs/{org_slug}/portal/shield/subjects/{ip_service_id}/profileGet subject profile
PATCH/api/v1/orgs/{org_slug}/portal/shield/subjects/{ip_service_id}/profilePromote subject to a customer-editable custom profile (idempotent)
POST/api/v1/orgs/{org_slug}/portal/shield/subjects/{ip_service_id}/rulesCreate subject rule
DELETE/api/v1/orgs/{org_slug}/portal/shield/subjects/{ip_service_id}/rules/{rule_id}Delete subject rule
GET/api/v1/orgs/{org_slug}/portal/external-serversList external servers
GET/api/v1/orgs/{org_slug}/portal/external-servers/regionsRegions an existing external server can be moved to
GET/api/v1/orgs/{org_slug}/portal/external-servers/{service_id}Get external server
PATCH/api/v1/orgs/{org_slug}/portal/external-servers/{service_id}Change where players are forwarded, how, and through which region
GET/api/v1/orgs/{org_slug}/portal/external-servers/{service_id}/custom-domainGet custom domain
POST/api/v1/orgs/{org_slug}/portal/external-servers/{service_id}/custom-domainLink custom domain
PATCH/api/v1/orgs/{org_slug}/portal/external-servers/{service_id}/custom-domainShow the vanity hostname, or the hosted one, in the public server list
DELETE/api/v1/orgs/{org_slug}/portal/external-servers/{service_id}/custom-domainUnlink custom domain
POST/api/v1/orgs/{org_slug}/portal/external-servers/{service_id}/custom-domain/verifyCheck the CNAME now rather than waiting for the next maintenance pass
POST/api/v1/orgs/{org_slug}/portal/external-servers/{service_id}/testStatus-ping the backend now and record the answer
GET/api/v1/orgs/{org_slug}/portal/platform-domainsOverview
GET/api/v1/orgs/{org_slug}/portal/platform-domains/availabilityCheck availability
POST/api/v1/orgs/{org_slug}/portal/platform-domains/claimsCreate claim
DELETE/api/v1/orgs/{org_slug}/portal/platform-domains/claims/{claim_id}Delete claim