Let customers run their servers
Build a server panel in your storefront, with power and console, files, snapshots, databases, schedules, ports, software and sharing.
Your storefront's server panel runs on the portal API. It gives a customer the controls that the App Deployment guides describe for a server in the Coritan dashboard, on the servers they bought from you. Every route on this page is under /portal/servers/{uuid}/ or /portal/snapshots/, and takes the customer's token as Authorization: Bearer $CUSTOMER_TOKEN.
Most routes have the same path after {uuid} as the platform's /api/v1/client/servers/{uuid}/ routes and take the same body, so the API sections of the App Deployment guides for servers apply once you change the base path and the token. This page covers what the portal does differently, and the routes those guides do not have.
Before you begin
Section titled Before you begin- Get a customer token as Sign customers in to your storefront describes.
- Find the server's
uuid. It isresource.uuidon the customer's service; see Show the customer's services.
Open a server
Section titled Open a servercurl "https://api.coritan.com/api/v1/orgs/acme/portal/servers/$SERVER_UUID" \
-H "Authorization: Bearer $CUSTOMER_TOKEN"
The answer is the server as the customer should see it: its name, status, plan limits such as memory_mb and disk_mb, and its join_address. On a free server, ip_address and port are empty and players use the join_address. entitlements lists what the plan includes, and one the plan leaves out has allowed: false with a reason and a detail to show in its place. legacy_backup_count says whether the server has backups worth a tab, sleep holds a free server's sleep state, and lock says whether we have locked the server.
Three fields say what this customer may do:
| Field | Meaning |
|---|---|
is_owner |
true for the customer whose service it is, and false for a customer the owner shared it with. |
permissions |
["*"] for the owner. Otherwise, the permissions the owner granted. |
capabilities |
One true or false flag per permission, named with underscores, such as file_read_content. Show or hide each control by these flags. |
GET /resources returns the power state and live use of CPU, memory, disk and network, in the shape Use the console and power controls describes. Poll it while the panel is open. GET /status-ping asks a Minecraft server who is online, and never fails: reachable is false when the game does not answer.
Who can do what
Section titled Who can do whatThe owner can do everything. A customer the owner shared the server with can do what their permissions allow, and a request outside them answers 403 with Insufficient permissions. GET /permissions lists every permission an owner can grant.
| Permission | Lets the customer |
|---|---|
control.console |
Send console commands. |
control.start, control.stop, control.restart |
Send that power signal. kill needs control.stop. Waking and keeping a free server need control.start. |
websocket.connect |
Open the live console. |
file.read, file.read-content |
List files, and read or download them. |
file.create, file.update, file.delete, file.archive |
Upload, create and copy files, or download one from a URL. Edit, rename and change modes. Delete. Compress and extract. |
file.sftp |
Connect over SFTP. |
snapshot.read, snapshot.create, snapshot.restore, snapshot.download, snapshot.delete |
List, take, restore, download and delete snapshots. Locking one needs snapshot.delete. |
backup.read, backup.restore, backup.download, backup.delete |
List, restore, download and delete old backups. Locking one needs backup.delete. |
database.read, database.create, database.update, database.delete |
List databases, create them, rotate their passwords and delete them. |
database.view_password |
See a database's password, and use the SQL editor and table browser. |
schedule.read, schedule.create, schedule.update, schedule.delete |
See schedules and their runs, create them, change them and their tasks or run them now, and delete them. |
allocation.read, allocation.create, allocation.update, allocation.delete |
See ports, add or request one, choose the primary port or publish one, and release one. |
startup.read, startup.update |
See the Java memory and startup flags, and change them and the startup variables. |
settings.rename |
Rename the server and change its join address subdomain. |
settings.reinstall |
Reinstall the server, change its software, install and remove add-ons, and run world optimization. |
Viewing the server, its resources, its sleep state, its software and its custom domain needs no particular permission. Only the owner can share the server, list it in your server directory, and link a custom domain.
Warning
Snapshots belong to the owner's account, not to one server. A customer with snapshot.restore on one server can restore any of the owner's snapshots onto it, including one taken from another server.
Share a server
Section titled Share a serverThe owner shares a server with another customer of your organization by username or email address:
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/servers/$SERVER_UUID/users" \
-H "Authorization: Bearer $CUSTOMER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"username_or_email": "alex@example.com", "permissions": ["control.console", "control.start", "control.stop", "file.read", "file.read-content"]}'
A customer with that username or email gets access at once, and we email them. An email address with no customer behind it gets a pending invite instead, with kind set to invite and an expires_at 14 days away. The invite becomes access when a customer with that address signs in to your storefront.
GET /userslists who has access, each withid,kind(grantorinvite),status,username,emailandpermissions. A shared customer sees the others by username only, without emails or invites.PUT /users/{subuser_id}withpermissionsreplaces what a customer may do.DELETE /users/{subuser_id}removes a customer or cancels an invite, and we email a customer who was removed. A shared customer can remove themselves the same way, or withPOST /users/leave.
Share a server with other users explains each permission in the words the Coritan dashboard uses.
Power and the console
Section titled Power and the consoleSend a power signal with POST /power:
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/servers/$SERVER_UUID/power" \
-H "Authorization: Bearer $CUSTOMER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"signal": "restart"}'
signal is start, stop, restart or kill, and the answer confirms only that we sent it, so read GET /resources for the result. POST /command runs a console command, given as command in the body or in the query string, up to 1,000 characters.
For the live console, GET /websocket returns the socket's address as socket, and the customer's token as token. Open the socket with the token in the token query parameter:
const res = await fetch(`https://api.coritan.com/api/v1/orgs/acme/portal/servers/${uuid}/websocket`, {
headers: { Authorization: `Bearer ${customerToken}` },
});
const { socket, token } = await res.json();
const ws = new WebSocket(`${socket}?token=${encodeURIComponent(token)}`);
ws.onopen = () => ws.send(JSON.stringify({ event: "send logs", args: [null] }));
ws.onmessage = (message) => {
const { event, args } = JSON.parse(message.data);
if (event === "console output") showLine(args[0]);
};
The socket speaks the events that Use the console and power controls lists. Send send command and set state only when the customer holds control.console or the power permission, because the socket refuses them otherwise. When the owner removes a customer's access, the socket closes with code 4003. Do not reopen a socket that closed with 4001, 4003, 4008, 4010 or 4011 until something changes: the token expired, access ended, the session ended, or the server is suspended or gone.
Wake a free server
Section titled Wake a free serverA free server sleeps when nobody plays on it, as How free servers work explains. GET /sleep returns its sleep state: the phase (awake, starting, queued or asleep), sleeps_at to count down from, and the customer's place in the start queue. A paid server answers with policy set to none.
POST /wakeasks for the server to start. Whensleep.challenge_requiredistrue, show the Cloudflare Turnstile challenge first, with the site key fromGET /storefront/branding, and send its answer asturnstile_token.POST /keepmarks a free server as still in use, which resets its count of days without activity. On a server we suspended for inactivity, it also reactivates the server and cancels its deletion. A paid server answers409.
A free server with an active paid add-on is not suspended or deleted for inactivity. The add-on is paid for, so the server stays while it lasts, and the inactivity count starts again when the last add-on ends.
Files and SFTP
Section titled Files and SFTPThe file routes are the ones Manage server files describes, under /files/. Upload with a multipart form, one files part per file:
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/servers/$SERVER_UUID/files/upload?directory=/plugins" \
-H "Authorization: Bearer $CUSTOMER_TOKEN" \
-F "files=@EssentialsX.jar"
Each file can be up to 10 GB, and one request up to 10 GB in all; a request over that answers 413 with {"error":"Request body too large"}. When some files of a batch fail after others have landed, the answer lists the uploaded names in files and the others in failed. POST /files/pull downloads a file from a URL onto the server, GET /files/pull/status reports on it and POST /files/pull/cancel?download_id=... stops it.
GET /sftp returns the host, port and username to connect with. The username is the customer's own email address and a short server reference, so a shared customer signs in as themselves. The password is the customer's storefront password. A customer who signed up with a social account has none until they set one; see Social sign-in. Connect to a server with SFTP covers the rest.
Take and restore snapshots
Section titled Take and restore snapshotsA snapshot saves the server's files to the account, and can be restored onto any of the owner's servers. The Snapshots guides explain how they work and what the storage allowance counts.
- Check that one fits with
GET /snapshots/estimate. It answersestimated_bytes,remaining_bytes,fitsandshortfall_bytes. The estimate is the server's disk use before compression, so the snapshot is never larger. - Take it:
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/servers/$SERVER_UUID/snapshots" \
-H "Authorization: Bearer $CUSTOMER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Before the 1.21 update", "ignored_files": ["logs"], "is_locked": true}'
name is up to 191 characters, ignored_files leaves out up to 50 paths, and is_locked keeps the snapshot from being rotated away. With no body, the snapshot is called manual-snapshot.
| Route | What it does |
|---|---|
GET /snapshots |
Lists this server's snapshots. scope=account lists every snapshot the owner holds, each marked for whether it can restore onto this server. A shared customer gets this server's list either way. |
POST /snapshots/{snapshot_uuid}/restore |
Restores any of the owner's snapshots onto this server. truncate (default true) deletes the current files first, and allow_mismatch confirms a restore from the same game on another loader. |
GET /snapshots/{snapshot_uuid}/download |
Returns a short-lived download url. |
POST /snapshots/{snapshot_uuid}/lock |
Locks or unlocks a snapshot. |
DELETE /snapshots/{snapshot_uuid} |
Deletes a snapshot. |
GET /snapshots/seed |
For a server ordered from a snapshot, the restore it is waiting on or has finished. POST /snapshots/seed/retry runs a failed one again. |
The account routes under /portal/snapshots/ do the same for the customer's own snapshots without naming a server, so they work after the server has gone. GET /portal/snapshots lists them, GET /portal/snapshots/allowance returns the storage allowance, and GET, DELETE, POST .../lock and GET .../download act on one. POST /portal/snapshots/{snapshot_uuid}/restore needs target_server_uuid, because there is no server in the path to restore onto.
Automatic backups
Section titled Automatic backupsWith automatic backups, we take one snapshot of the server every day and keep the newest 7. Each one is named Automatic backup followed by the date, shows as taken by the platform, and the customer restores, downloads, locks and deletes it like any other snapshot. It counts toward the customer's snapshot storage, as Snapshot storage allowance explains.
A server has them only while it carries the Automatic Server Backups add-on. Your own account plan does not give them to your customers. A server has them only while it carries the add-on, a free server included. A server without the add-on gets none, and ending the add-on stops them. The backups already taken stay until the customer deletes them.
| Route | What it does |
|---|---|
GET /auto-backups |
Answers whether the server has automatic backups, whether they run and the newest backup. Needs snapshot.read on a shared server. |
PUT /auto-backups |
Pauses or resumes them with {"enabled": false} or {"enabled": true}, and answers as GET does. Only the server's owner can use it. A server without automatic backups answers 409 with the reason in detail. |
curl "https://api.coritan.com/api/v1/orgs/acme/portal/servers/$SERVER_UUID/auto-backups" \
-H "Authorization: Bearer $CUSTOMER_TOKEN"
{
"available": true,
"enabled": true,
"paused": false,
"source": "addon",
"reason": null,
"message": null,
"frequency": "daily",
"keep": 7,
"last_run_at": "2026-10-01T03:07:12",
"next_run_at": "2026-10-02T03:07:12",
"last_backup": {
"id": "7f1c2e0a-4b0d-4f0e-9d63-2b0f3c7c1a11",
"created_at": "2026-10-01T03:07:12",
"size_bytes": 1073741824
}
}
available is true when the server has automatic backups. enabled is true while the daily run is on, and paused is true when the customer switched it off. Pausing does not end the add-on or its billing. source is addon, or null when the server has none. last_backup is the newest finished backup, or null, and its id is the snapshot's uuid. The times are in UTC.
reason says in one word why there are none, or why the last run took none, and message says it in a sentence to show the customer:
reason |
Meaning |
|---|---|
not_included |
The server has no automatic backups. They are an add-on the customer can buy for it, on a free server too. |
snapshots_off |
Snapshots are switched off on the platform, so no backup runs. |
no_room |
The last run did not fit in the customer's snapshot storage. We try again six hours later. |
When a new backup does not fit, we first delete the server's own oldest automatic backups, but only when that makes enough room. We never delete a snapshot the customer or a schedule took, a locked backup, or the server's newest backup. When that is not enough we take none and reason is no_room. The customer deletes a snapshot or adds snapshot storage, and the next run takes the backup.
Add-ons
Section titled Add-onsYour storefront can sell three add-ons for a server: extra CPU, automatic backups and Server Management. A customer buys one for one of their own servers, and you bill it on your invoice, in your currency, at your price. Nothing is charged to your Coritan account, and the add-on is a service of its own that the customer cancels like any other (Cancel a service). Set up products and pricing explains how you sell them. A customer who has not ordered yet can buy the same add-ons at checkout: Offer add-ons with a server shows how.
| Add-on | kind |
What it gives | Most per server |
|---|---|---|---|
| Extra CPU | container_cpu |
One more vCPU for each unit. We raise the server's CPU limit by 100 percent of one core for each unit. | 8 |
| Automatic backups | container_auto_backups |
A snapshot every day. See Automatic backups. | 1 |
| Server Management | server_management |
The customer asks your team by ticket to install and set up plugins, mods and modpacks. We mark the ticket, as Server Management explains. | 1 |
Only the server's owner can use these routes. A customer the server was shared with gets 403. A free server takes add-ons too, on billing of their own (Add-ons on a free server).
| Route | What it does |
|---|---|
GET /addons |
Lists what the store sells for this server, with prices, and the add-ons the server has. |
POST /addons/preview |
Prices an order without placing it. Takes kind, quantity and, on a free server, billing_cycle. |
POST /addons |
Places the order. Takes kind, quantity and, on a free server, billing_cycle. It answers as POST /storefront/order does: service_id, invoice_id, requires_payment, total, currency and due_date, with billing, billing_cycle and renewal_amount added. |
curl "https://api.coritan.com/api/v1/orgs/acme/portal/servers/$SERVER_UUID/addons" \
-H "Authorization: Bearer $CUSTOMER_TOKEN"
{
"server": {"uuid": "9f3c0a52-...", "name": "survival", "billing_cycle": "monthly", "next_due_date": "2026-11-05T12:00:00Z", "free": false, "plan_name": "CX2"},
"offers": [
{
"kind": "container_cpu",
"org_product_id": 41,
"name": "Extra CPU",
"description": "One more vCPU on one App Deployment server.",
"adds": "+1 vCPU",
"unit_label": "vCPU",
"max_quantity": 8,
"attached_quantity": 0,
"billing": "server",
"price": {"org_pricing_id": 118, "billing_cycle": "monthly", "amount": "1.99", "currency": "USD"},
"prices": [{"org_pricing_id": 118, "billing_cycle": "monthly", "amount": "1.99", "currency": "USD"}],
"orderable": true,
"reason": null,
"message": null,
"applies": "now",
"notes": ["The server is running. We send the new limit to it as soon as the invoice is paid. If it does not show the extra CPU, restart it."]
}
],
"attached": []
}
billing says how the add-on is billed. It is server on a paid server: the add-on uses the plan of the server's billing period, so a customer on an annual server sees the annual price, and prices holds that one price. It is own on a free server (Add-ons on a free server), and prices then lists every price you sell for the add-on, one for each billing period. price is the price the order uses when billing_cycle is left out, and price.amount is for one unit. max_quantity is how many more the server takes now, counting orders that wait for payment. When orderable is false, reason says why and message is a sentence to show the customer:
reason |
Meaning |
|---|---|
snapshots_disabled |
Automatic backups save snapshots, and snapshots are off on the platform. |
max_reached |
The server has the most of this add-on it takes. |
unpaid_order |
An order for this add-on waits for payment. The customer pays it or cancels it first. |
no_price_for_cycle |
You have no price for the server's billing period. Add one under Set up products and pricing. |
server_not_ready |
The server is not active, is still installing, is set to end, is due for renewal, or has no renewal date. |
not_available |
The server's plan has no CPU limit to add to, or the add-on is sold only for other games than the server's. Server Management is sold for Minecraft servers by default. |
applies says when the effect reaches the server. For extra CPU it is now while the server is running: we queue a push of the new limit to the server's host when the order is paid, and the host is told to update the running server. A restart makes sure the server uses it. It is after_restart for a stopped server, which uses the new limit from its next start. Automatic backups and Server Management are now. attached lists the add-ons already on the server: org_service_id, kind, quantity, status, the amount it renews at, next_due_date, and cancel_at_end once the customer set it to end with the term.
POST /addons/preview answers with what the order costs:
{
"kind": "container_cpu",
"quantity": 2,
"billing": "server",
"billing_cycle": "monthly",
"unit_price": "1.99",
"due_now": "2.70",
"tax": "0.00",
"renewal_amount": "3.98",
"renews_at": "2026-11-05T12:00:00Z",
"days_remaining": 21,
"days_in_cycle": 31,
"currency": "USD",
"adds": "+2 vCPU",
"applies": "now"
}
On a paid server, due_now is the price for the rest of the server's current billing period, with your tax rate added. The add-on then renews on the server's renewal date, renews_at, at the full renewal_amount, so both are invoiced together. An order for the last day of a period costs nothing now and starts at once.
A paid server takes the add-on in its own billing period only. A billing_cycle that is not the server's answers 422 and names the period to use, so leave it out.
The order refuses with 409 and a reason from the table for a server that cannot take it, and with 422 for a kind your store does not sell or a quantity the server cannot take, such as This server takes 1 more vCPU of Extra CPU. Set quantity to 1. A store with its orders held answers 403. POST /addons/preview and POST /addons share one limit for each customer and server, 20 requests in 10 minutes, and answer 429 past it. The generic POST /storefront/order, your staff's POST /orgs/{org_slug}/services and the admin order route refuse an add-on product with 422 and Order add-ons from the server's page., and the catalogue leaves add-on products out, so no one buys one without a server.
The effect starts when the customer pays the invoice. Extra CPU raises the server's limit to the plan's own CPU plus the add-ons, and a plan change on the server keeps the add-ons on top of the new plan. The effect ends when the customer cancels the add-on (at once, or with the term), when its renewal goes unpaid and we suspend it, and when the server ends. A server that is cancelled or deleted ends its add-ons the same way: now, or at the end of the term the server is cancelled for. A server that moves to your free plan ends its add-ons too, except the ones the customer keeps (Keep add-ons when a server moves to free). POST /portal/services/{id}/keep on the server also withdraws the end date of the add-ons that were set to end with it.
An add-on is never moved to another plan. POST /portal/services/{id}/change-plan, GET /portal/services/{id}/plan-preview and the staff plan change on an add-on answer 409 with Add-ons cannot be moved to another plan. Cancel it and order the one you want., and GET /portal/services/{id}/plans answers an empty list. The customer cancels the add-on and orders the one they want from the server's page.
An add-on renews with the server. When the customer pays the first invoice after the server has already renewed, the add-on takes the server's new renewal date, so the customer is not charged again for time that invoice covered. When your team applies a new price to the subscriptions already on a plan, an add-on renews at the new price for each unit it holds, and the customer's notice and the audit log say that total.
When the server is cancelled now and your storefront credits the unused days of a cancellation, each add-on's unused days are credited the same way, up to what the customer paid for that add-on. A server that ends without a credit, such as a staff termination, gives its add-ons none.
Add-ons on a free server
Section titled Add-ons on a free serverA free server takes the same add-ons as a paid one. It has no price and no renewal date to line up with, so each add-on is billed on its own: billing is own on its offers, its preview and its order.
- The customer chooses the billing period.
GET /addonslists every price you sell inprices, and the order'sbilling_cyclepicks one of them, such asmonthly,quarterlyorannually. Leavebilling_cycleout and the order usesmonthly. When you have no monthly price, it uses the first price inprices. A period you have no price for answers422withreasonset tono_price_for_cycle. - The first invoice charges the full price for one period, counted from the day the customer pays. It is never prorated, so
days_remaininganddays_in_cyclearenullon the preview. - The add-on renews on that date and never moves to the server's date. It keeps its own date when the server later moves to a paid plan, and the server's renewal does not change it.
- The effect is the same as on a paid server. Extra CPU adds to the free plan's CPU once the invoice is paid, automatic backups take a snapshot every day, and Server Management works as Add-ons describes.
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/servers/$SERVER_UUID/addons" \
-H "Authorization: Bearer $CUSTOMER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"kind": "container_auto_backups", "quantity": 1, "billing_cycle": "annually"}'
Keep add-ons when a server moves to free
Section titled Keep add-ons when a server moves to freeWhen your organization sells a free plan, cancelling a paid server moves it to the free plan instead of ending it. The customer can keep some of its add-ons on the free server, on billing of their own.
GET /portal/services/{service_id}/cancel-preview lists the server's add-ons in addons, so the cancel page can ask which to keep. Each entry has org_service_id, name, kind, quantity, amount, billing_cycle and next_due_date. The list is empty for a server with none, and an add-on that waits for payment is on it too.
POST /portal/services/{service_id}/cancel takes keep_addon_ids, a list of those org_service_id values:
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/services/41/cancel" \
-H "Authorization: Bearer $CUSTOMER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"immediate": true, "keep_addon_ids": [88]}'
- When the server moves to free, the add-ons named stay active. Each one changes to
billingown, loses any end date and renews on its own date at its own price. Every other add-on ends with the server. - Leave
keep_addon_idsout, or send an empty list, to end every add-on. - When the server is deleted,
keep_addon_idsis ignored and every add-on ends. That is the case when the customer setsdelete_server, and when your organization sells no free plan. - An id that is not one of this server's add-ons answers
422, and nothing is cancelled. - An end-of-term cancellation takes the same field. The add-ons named are not set to end, and the rest end with the term.
The sweep that moves a server to free for an unpaid renewal keeps its add-ons that are paid for and active, the same way. An add-on that still waits for payment ends.
Backups
Section titled BackupsSnapshots have replaced backups, so POST /backups answers 410 with reason set to replaced_by_snapshots. A server that took backups before keeps them: GET /backups lists them, and restore, download, lock and delete work as Manage server backups describes.
Databases
Section titled DatabasesPOST /databases creates a database on the server's database host. name_suffix names it with lowercase letters, digits and underscores, up to 48 characters (default db), and remote limits where it accepts connections from (default %, anywhere). Send an Idempotency-Key header so a retry does not create two.
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/servers/$SERVER_UUID/databases" \
-H "Authorization: Bearer $CUSTOMER_TOKEN" \
-H "Idempotency-Key: 1f9e6c2a-luckperms" \
-H "Content-Type: application/json" \
-d '{"name_suffix": "luckperms"}'
| Route | What it does |
|---|---|
GET /databases |
Lists the server's databases. |
GET /databases/{database_id}/credentials |
Returns the password and connection details. |
POST /databases/{database_id}/rotate-password |
Sets a new password. |
POST /databases/{database_id}/retry |
Tries a failed creation again. |
DELETE /databases/{database_id} |
Deletes the database. |
POST /databases/{database_id}/query |
Runs the SQL in sql, up to 65,536 characters, as the database's own user. |
GET /databases/{database_id}/tables |
Lists tables. .../tables/{table}/columns describes one. |
GET /databases/{database_id}/tables/{table}/rows |
Reads rows: limit (1–100, default 50), offset, search, sort and direction. |
POST, PATCH /databases/{database_id}/tables/{table}/rows |
Inserts a row from values, or changes the row whose primary key is key. |
POST /databases/{database_id}/tables/{table}/rows/delete |
Deletes the row whose primary key is key. |
GET /databases/{database_id}/tables/{table}/export.csv |
Downloads the table as CSV. X-Export-Truncated: 1 means it holds only part of the table. |
POST .../tables/{table}/truncate, DELETE .../tables/{table} |
Empties or drops a table. |
Create and manage server databases explains the database host and the connection details.
Schedules
Section titled SchedulesSchedules run tasks on a timetable, as Schedule server tasks describes, with the same body under /schedules/:
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/servers/$SERVER_UUID/schedules" \
-H "Authorization: Bearer $CUSTOMER_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", "tasks": [{"action": "command", "payload": {"command": "say Restarting in 60 seconds"}}, {"action": "power", "payload": {"signal": "restart"}, "time_offset": 60}]}'
A shared customer needs schedule.update to change tasks, and also the permission each task's action needs by hand: control.console for a command, the power permission for a power task, and snapshot.create for a snapshot. That way a schedule never does what the customer could not do themselves. GET /schedules/{schedule_uuid}/runs lists recent runs, newest first, and POST /schedules/{schedule_uuid}/execute runs a schedule now.
Ports
Section titled PortsGET /allocations lists the server's ports and how many it may have, and GET /allocations/available-ports lists free ones. POST /allocations adds a port, with an optional port, notes, and on_dedicated_ip to open it on the server's Floating IP. POST /allocations/{allocation_id}/primary makes a port the one players connect to, POST /allocations/{allocation_id}/publish-port publishes one on the game's default port of a Floating IP, and DELETE /allocations/{allocation_id} releases it. Manage server ports explains each.
Your organization can decide that extra ports on free servers go through a request to your staff instead. GET /allocations/request then answers required: true, with the customer's open_request if they have one, and POST /allocations answers 403 with reason set to request_required. Send the request with a note that says what the port is for:
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/servers/$SERVER_UUID/allocations/request" \
-H "Authorization: Bearer $CUSTOMER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"note": "A second port for the web map plugin"}'
note is 3–500 characters. The request opens a conversation in your support inbox tagged port-request, and a second request while one is open returns the first. Your staff add the port as Manage customer servers in the staff console describes.
Software and add-ons
Section titled Software and add-onsThe software routes are the ones Change the server software and the guides after it describe, under /software/. Reading them needs only access to the server. Changing what the server runs, installing, updating and removing add-ons, resource packs, safe mode and Bedrock packs need settings.reinstall, and the Java memory and startup flags at GET and PATCH /software/jvm need startup.read and startup.update.
GET /software/contextsays what the server runs now.GET /software/catalog,.../catalog/{key}/versionsand.../versions/{version}/buildslist what it can change to, andPOST /software/changechanges it. A change that wipes the server needsconfirm_server_name.POST /software/searchfinds add-ons,GET /software/versions/{source_uuid}/{identifier}lists an add-on's versions andGET /software/dependencies/{source_uuid}/{identifier}what one needs.POST /software/installinstalls one;modeset toreplacewipes the server first and needsconfirm_server_name.GET /softwarelists what we installed,GET /software/addonsevery add-on on disk, andGET /software/updatesnewer builds.GET /software/installsfollows install jobs, which.../canceland.../retryact on.POST /software/bedrockwithenabledturns Bedrock crossplay on or off for a Java server.PUT /software/bedrock/channelwithchannelset toreleaseordevelopmentchooses where Geyser comes from, as Use Geyser development builds describes.POST /software/viawithenabledturns ViaVersion and ViaBackwards on or off. Every plugin server has them unless the customer turns them off, as Let players on other Minecraft versions join describes;GET /software/contextreports them undervia_plugins.
Import files from another host
Section titled Import files from another hostPOST /import/test-connection and POST /import/start take the body Import a server from another host describes. Testing needs file.read and starting needs file.create. truncate set to true wipes the server first, so it also needs file.delete and confirm_server_name: the server's name, compared without regard to case. A missing or wrong name answers 400 with Type the server name to confirm a wipe and import, before the rate limit counts the request. start ignores the name without truncate, test-connection ignores it, and a server whose install never finished needs none. start also takes skip_existing, which leaves out files already on the server with the same size, and host_key_fingerprint, the SFTP key the customer confirmed from the host_key that test-connection returns. The import refuses a host that shows another key before it sends any password. The wipe keeps every entry whose name starts with a dot or with backups, and on a free server the server executable the panel installed. GET /import/current returns the running import, GET /import/status/{import_id} reports on one, and DELETE /import/{import_id} stops it. The status has errors for files that could not be copied and skipped for entries left out on purpose (symbolic links, devices, sockets, pipes and the free server's executable), each as file and reason, which never count towards the 200 failed files. While an import is pending, in_progress or cancelling, POST /power with start or restart and POST /wake answer 409 with A file import is running on this server. Start it again when the import finishes. stop and kill still work.
Change the server's settings
Section titled Change the server's settings| Route | What it does |
|---|---|
PATCH /settings |
name renames the server: 2–48 characters with at least one letter or number. world_optimization_enabled turns world optimization on or off. A free server can turn it on but not off. |
PATCH /subdomain |
Changes the first part of the join address: 3–28 lowercase letters, digits and hyphens, not only digits. |
GET /startup-variables |
Every startup variable the server runs with and what its owner may do with each, as the client API answers it (Read startup variables). Needs startup.read. |
PATCH /startup-variables |
Changes or adds startup variables, as variables, and removes the names in remove_variables, under the rule Change startup variables describes: every variable but the platform's own, the Java settings', safe mode's and, on a free server, the one naming the program it runs. They apply at the next restart. Needs startup.update. |
POST /reinstall |
Deletes every file and installs the server again. Send the server's name as confirm_server_name; case does not matter. A server whose install never finished needs no name. |
GET /world-optimization |
What removing unused chunks has done to the world, with its recent runs. |
POST /world-optimization |
Runs world optimization now, once a day, on a stopped server with the setting on. |
GET /server-icon |
The Minecraft server icon, server-icon.png: present, valid (Minecraft shows only a 64×64 PNG), icon as a data URI, and problems for a file the game ignores. support.supported is false on Bedrock and other games. Needs file.read. |
POST /server-icon/preview |
Takes a picture as the multipart field file, up to 10 MB, and answers with a 64×64 PNG for each fit in variants, without saving. SVG is refused, so draw it as a PNG in the browser first. Needs file.create. |
POST /server-icon |
Takes file and a fit of cover, contain or stretch, and saves the icon as server-icon.png. It shows after the next restart. Needs file.create. A file that is not a picture answers 400, and one over 10 MB answers 413. |
DELETE /server-icon |
Removes the icon. Needs file.delete. |
Link a custom domain
Section titled Link a custom domainThe owner can give the join address a name on their own domain, such as play.example.com.
- Read
GET /custom-domain.expected_cname_targetis the address the customer's DNS record must point at. - Ask the customer to add a CNAME record for their name, pointing at that address. For a domain's root, use a DNS provider that flattens CNAME records or offers ALIAS records.
- Link it:
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/servers/$SERVER_UUID/custom-domain" \
-H "Authorization: Bearer $CUSTOMER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"domain": "example.com", "subdomain": "play", "include_in_list": true}'
subdomain defaults to @, the domain itself. The answer has verification_status: verified when we can see the record, or pending until we can. We keep checking, and POST /custom-domain/verify checks now. When the record stays missing across several checks, the status becomes failed, the name stops working and we email the owner once. It works again by itself when the record returns.
include_in_list shows the custom name in your server directory in place of the join address; change it later with PATCH /custom-domain. DELETE /custom-domain unlinks the name. Changing the server's subdomain moves the address the record must point at, so we email the owner the new one and the name waits as pending until the record moves.
List the server in your directory
Section titled List the server in your directoryWhen your organization runs a public server directory, the owner decides whether their server appears in it. GET /listing returns hidden, the chosen tags, the available_tags and max_tags, and feature_enabled. PATCH /listing with hidden and tags changes them. Listing a server needs at least one of your tags and a join address. Run a community with a server list, forum and guides covers the directory itself.
Result
Section titled ResultYour storefront shows each server with the controls its customer may use, and every request is checked against the owner's grant, so a control your panel shows by mistake still cannot do more than the grant allows.
Troubleshooting
Section titled Troubleshooting401on every call- The customer token is missing, expired or belongs to another organization. See Troubleshooting for customer sign-in.
403withAccess denied- The customer neither owns the server nor has it shared with them. Check the
uuidagainst their services. 403withInsufficient permissions- The owner has not granted the permission this route needs. Hide the control when its
capabilitiesflag isfalse. Only the server owner can manage subusers- A shared customer tried to share the server or change its directory listing. Only the owner can.
409withreasonset toserver_deleting- The server is being deleted. Every route refuses it from then on.
423withreasonset toserver_locked- We locked the server while we review activity that broke our acceptable use policy. Its files stay. Show the
message. Type the server name to confirm a wipe and reinstallconfirm_server_nameis missing or does not match the server's name.409withreasonset toworld_optimization_running- We are removing unused chunks and the server cannot start until that ends. Try again after
retry_after_seconds. File exceeds maximum upload size of 10 GB- Upload the file over SFTP, or download it onto the server from a URL.
Downloading a snapshot is not included on the free plan. …- Free plans leave out snapshot downloads. The customer can still restore the snapshot, or download files one by one from the file routes.
403withreasonset torequest_required- Your organization asks for port requests on free servers. Send one with
POST /allocations/request. Extra ports on this server are added from the Network tab, not requested- Port requests are off for this server. Add the port with
POST /allocations. No matching account in this organization- No customer has that username. Use their email address instead, which sends an invite when they have no account yet.
409withPlease try again in a moment.- Adding a user on
POST /usersmet another change to the same records and gave up after three tries. Nothing was added. Send the request again. This server does not sleep; use the power action to start it.- The server is on a paid plan. Start it with
POST /power.