Let customers manage IPs, protection and domains
The portal API for a customer's Floating IPs, DDoS Shield, external servers and platform domain names.
These portal routes let a customer order and attach Floating IPs, give a Cloud Compute instance its IPv6, 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.
Before you begin
Section titled Before you begin- 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_idfromGET /portal/ips. The external server routes take the serviceidfromGET /portal/external-servers, which is the idGET /portal/servicesreturns too.
Order a floating IP
Section titled Order a floating IPList the pools that sell what the customer wants:
Shellcurl "https://api.coritan.com/api/v1/orgs/acme/portal/ips/pools?version=ipv4&prefix_len=32" \ -H "Authorization: Bearer $CUSTOMER_TOKEN"Each pool in
poolshas itsid,name,region,country_codeandgeo_label, andavailablesays whether it has an address to sell.versionisipv4(the default) oripv6. For IPv4,prefix_lenis32for one address, or 24–29 for a subnet; the list is empty for subnets while we do not rent them. IPv6 is sold as single addresses, so withversion=ipv6sendprefix_len=128or leave it out; any other size answers422.regionnarrows the list to one location. IP pools and regions describes the pools.Find the
idof your IP product and of its plan, as List products and plans shows.Create a key for this checkout in the browser, such as the value of
crypto.randomUUID(), and place the order:Shellcurl -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_idYour IP product and the plan. pool_idThe pool from step 1. The address comes from this pool only. prefix_len,versionThe size and family. They default to what the product sells, so leave them out for an IPv6 address. A value the product does not sell answers 422.idempotency_keyRequired. 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_idOptional. The service idof one of the customer's servers that has no floating IP yet. We attach the address to it as soon as it is assigned. It works only for one IPv4 address, withprefix_lenset to32.hostnameOptional. A name for the service. The answer has the fields of a storefront order, such as
invoice_id,requires_paymentandstatus. It addsintent, which follows the order, andhold_expires_at.When
requires_paymentistrue, send the customer to pay, as Pay an invoice shows. We keep the address for the customer untilhold_expires_at. An order that stays unpaid past then is cancelled, and the address goes back to the pool.
Follow or cancel an order
Section titled Follow or cancel an order| 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.
Show a customer's addresses
Section titled Show a customer's addressesGET /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, block for a subnet, or routed for the IPv6 /64 that comes with a Cloud Compute instance. |
address, cidr, version |
The address, or the first address of a subnet or a /64, 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 server, whether the address has reached it: pending, delivered, failed or removing, and why it failed. |
ptr_hostname |
The reverse DNS name of a single address. A /64 has a name per address instead, as Set reverse DNS shows. |
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.
Attach and detach an address
Section titled Attach and detach an addressList what the address can go on:
Shellcurl "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_nameisvps) or servers (container), with itsservice_id,hostnameandhas_floating_ip.Attach the address:
Shellcurl -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_typeisvpsorcontainer, andtarget_service_idis the target'sservice_idfrom step 1. For a subnet,host_addresspicks the address in it; leave it out to use the first free one. On an instance, the first IPv4 address you attach becomes the primary one; sendis_primary: trueto make a later address primary, orfalseto keep it secondary. A server takes one floating IP.An IPv6 address attaches to Cloud Compute instances only, so send
target_typevps. It is never the instance's primary address: leaveis_primaryout.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 ashost_address.
An instance's IPv6 /64 (kind routed) stays on its instance. Attaching, detaching or moving it answers 409.
Attach and detach a floating IP explains what attaching does on each kind of target.
Set reverse DNS
Section titled Set reverse DNScurl -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.
For an instance's IPv6 /64, every request names one address inside it as host_address, such as 2001:db8:1000:5::1, the address the instance uses. Send it in the body of PATCH and as ?host_address= on GET. The /64 keeps up to 256 names.
Give an instance its IPv6
Section titled Give an instance its IPv6A Cloud Compute instance in a location with IPv6 gets an IPv6 /64 of its own when it is created, at no charge, and uses the first address in it from its next start. Add IPv6 to an instance explains how the /64 works on the instance.
GET /portal/vps/{uuid}shows the /64 asipv6_prefixand the address the instance uses asipv6_address. Both arenulluntil the instance has IPv6.GET /portal/ipslists the /64 as an item withkindset torouted, attached to its instance.- When you sell an IP product,
GET /portal/servicesalso lists the /64 as one of the customer's services, at no charge.
To give IPv6 to an instance that has none, such as one created before its location had IPv6:
Send the request, with the instance's
uuidfromresource.uuidon the customer's service. It takes no body:Shellcurl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/vps/$INSTANCE_UUID/ipv6" \ -H "Authorization: Bearer $CUSTOMER_TOKEN"JSON{"prefix": "2001:db8:1000:5::/64", "address": "2001:db8:1000:5::1", "reboot_required": true}When
reboot_requiredistrue, restart the instance, as Power an instance shows, so that it sets up the address. Asking again answers the same /64 withreboot_requiredset tofalse.
The request answers 409 when the location has no IPv6 to give or while the instance is migrating, 404 when the instance is not the customer's, and 429 with a Retry-After header when the customer asks too often. Any other failure answers 502, and the instance keeps no /64 from that request.
Filter traffic with DDoS Shield
Section titled Filter traffic with DDoS ShieldDDoS Shield filters traffic to every address with our standard profile. A customer can give an address a profile of their own, change its settings, add rules to it and block the senders they do not want.
| 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 and protection_mode. |
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, at most 500). |
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 when it has none, applies the settings in the body, and returns the profile. |
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. |
GET /portal/shield/subjects/{ip_service_id}/blocked-sources |
The senders blocked on the address in blocked_sources, newest first, and the most it can hold in max_blocks. |
POST /portal/shield/subjects/{ip_service_id}/blocked-sources |
Blocks a sender or a range on the address. |
DELETE /portal/shield/subjects/{ip_service_id}/blocked-sources/{block_id} |
Removes a block. |
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.
Each PATCH, POST and DELETE counts towards a budget of 60 DDoS Shield changes per customer in ten minutes. Past it, a change answers 429 with rate_limited in detail.error, and the Retry-After header gives the seconds to wait.
Change the profile's settings
Section titled Change the profile's settingscurl -X PATCH "https://api.coritan.com/api/v1/orgs/acme/portal/shield/subjects/$IP_SERVICE_ID/profile" \
-H "Authorization: Bearer $CUSTOMER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"default_action": "deny", "rate_limits": {"per_source_pps": 50000}}'
| Field | What it changes |
|---|---|
protection_mode |
standard filters with our own checks and rate limits. custom does the same with the profile's rate limits in place of ours. passthrough skips our checks, the rate limits, the rules and the blocks. |
default_action |
What happens to traffic that no rule matches: allow lets it through, and deny drops it. |
established_passthrough |
With default_action set to deny, true lets through TCP packets of connections already open, sent to port 1024 or above. |
rate_limits |
Any of per_source_pps (100–10,000,000), aggregate_pps (10,000–100,000,000), syn_pps_per_source (100–10,000,000) and icmp_pps_per_source (10–1,000,000). A limit you leave out keeps its value. The limits apply only in custom mode. |
Every field is optional, and an empty body gives the address its own profile without changing anything else. The answer has the same shape as GET. A word outside its list answers 422 with protection_mode must be standard|custom|passthrough or default_action must be allow|deny, and a limit outside its range answers 422 with a list in detail. Change a floating IP's DDoS protection explains each setting.
Add a rule
Section titled Add a ruleA 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:
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: one address, such as 203.0.113.7, or a range in CIDR form, such as 203.0.113.0/24. We store one address as its /32. |
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. We check the prefixes, and one that is neither an address nor a range answers 422 with a list in detail.
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.
Block a sender
Section titled Block a senderA block drops all traffic from an address or a range to one of the customer's addresses, on the standard profile or their own, unless the profile is in passthrough mode:
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/shield/subjects/$IP_SERVICE_ID/blocked-sources" \
-H "Authorization: Bearer $CUSTOMER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"address": "198.51.100.7", "reason": "Repeated SSH logins", "expires_minutes": 1440}'
| Field | What it holds |
|---|---|
address |
Required. An IPv4 address, or a range from /8 to /32. |
reason |
Optional, up to 255 characters. |
expires_minutes |
Optional. The minutes until we remove the block, from 1 to 43,200 (30 days). Leave it out to keep the block until the customer removes it. |
The answer, with status 201, is the block: its id, which DELETE takes, the cidr we stored, the reason, expires_at and created_at. An address holds up to 100 blocks. A block our team added has source set to admin and removable set to false, and the customer cannot remove it. Block a traffic source explains how a block is applied and lists every answer.
Attack emails
Section titled Attack emailsWe email the customer when an attack on one of their addresses starts and when it ends, as Attack emails describes for Coritan accounts. The emails leave the way your other customer emails do (Choose how your email leaves) and link to your storefront's sign-in page. A customer cannot turn them off. Each address gets emails about at most six attacks in any 24 hours.
Run an external server
Section titled Run an external serverAn 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:
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.
Link a domain to an external server
Section titled Link a domain to an external serverThe routes under /portal/external-servers/{id}/custom-domain work as Link a custom domain describes for a 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 domainWhen 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.
Read the overview:
Shellcurl "https://api.coritan.com/api/v1/orgs/acme/portal/platform-domains" \ -H "Authorization: Bearer $CUSTOMER_TOKEN"suffixeslists the domains on offer, andclaimsthe customer's names.limitis how many names a customer may hold, andremaininghow many more they can claim;remainingisnullwhen there is no limit.ruleshas themin_length,max_lengthandpatterna name must fit, and thereservednames nobody can claim. Once the customer has a name,suggestionsproposes a hostname under it for each product, such asplay.survival.example.comfor a game server.Check a name while the customer types it:
Shellcurl "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 asfqdn, and thereasonwhen it is not available.suffixdefaults to the first domain on offer.Claim it:
Shellcurl -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 thefqdnandzone_id.parent_delegatedsays whether we delegated the name from your domain's zone,dnssecwhether we signed it, andcertificatewhether we requested a wildcard certificate for it, with thereasonwhen 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.
Result
Section titled ResultYour 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, DDoS Shield changes and changes to external servers appear in your organization audit log with the customer as the actor.
Troubleshooting
Section titled TroubleshootingUnsuspend 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 tabattach_to_org_service_idworks only whenprefix_lenis32. Order a subnet without it.This server takes IPv4 floating IPs only for now. Attach this IPv6 address to a Cloud Compute instance.- The request sent an IPv6 address to a server, or named one in
attach_to_org_service_id. Attach it to one of the customer's Cloud Compute instances. An IPv6 address is never an instance's primary address. Leave is_primary out.- Send the attach again without
is_primary. This /64 comes with its instance and stays with it.- The address is an instance's IPv6 /64, which cannot be attached, detached or moved.
An IPv6 order takes prefix_len 128; got 64- IPv6 is sold as single addresses, for a pools list or an order. Send
prefix_lenas128, or leave it out. A PTR on a /64 needs host_address: the address inside the /64 that it names.- Add
host_address. An address outside the /64 answers422with a message such as2001:db8:2000::1 is not inside 2001:db8:1000:5::/64. This /64 already has 256 PTR records, the most it can keep. Set a new hostname on one of its addresses instead.- Change the name of an address the /64 already has a name for.
This location has no IPv6 yet.orThis location has no IPv6 left to give out. Contact support.- The instance's location has no /64 to give. Nothing changed on the instance.
We could not add IPv6 to this instance. Try again in a moment.- The instance did not get its /64. Send the request again in a moment.
PTR hostname must be a fully-qualified domain name- Send a full name, such as
mail.example.com. 403withRule does not belong to this subject profile- The rule is on another address's profile. Delete it through that address.
403withOur team added this block, so only they can remove it. Contact support to ask.- We added the block. Ask support to remove it.
409with198.51.100.7/32 is already blocked on this address.- The address has that block already.
409withThis address already has 100 blocked sources. Remove one before adding another.- Remove a block the customer no longer needs, or block one range that covers several senders.
422withThat range is too wide. Block a /8 or a narrower range.orThis address is IPv4, so block an IPv4 source.- Send an IPv4 address, or a range from
/8to/32. 422withEnter an IP address or a range in CIDR form, such as 203.0.113.7 or 203.0.113.0/24.- The block's
address, or a rule's prefix, is neither an address nor a range. For a rule, the message is themsgof an item indetail. 409withThis external server is not connected yet; pay its invoice first- The external server has no join address until its first invoice is paid.
409withThis external server is suspended- A suspended external server cannot be changed. Unsuspend its service first.
422witherrors- We refused the new address or setting, and
errorssays 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.
404withThis brand does not offer platform names- Your brand lends no domain yet. Ask support to add one.
409withAlready taken- Another customer holds the name, or a game server's join address uses it. Choose another.
429withrate_limited- The customer repeated an action too quickly, or made 60 DDoS Shield changes in ten minutes. Wait, then try again.