# Client API: Services

> Order and manage provisioned services (lifecycle, actions, cancel, plan changes).

Source: https://www.coritan.com/docs/api/reference/client/services/

Order and manage provisioned services (lifecycle, actions, cancel, plan changes).

Base URL: `https://api.coritan.com/api/v1`. Paths below are complete.

To try these requests in the browser, open the [interactive Client API reference](https://api.coritan.com/docs).

## Operations

| Method | Path | Summary |
| --- | --- | --- |
| GET | [`/api/v1/services/`](#op-get-api-v1-services) | List services |
| POST | [`/api/v1/services/order`](#op-post-api-v1-services-order) | Order a platform service, and any add-ons bought with it |
| GET | [`/api/v1/services/{service_ref}`](#op-get-api-v1-services-service-ref) | Fetch a service by numeric id OR underlying resource UUID (instance/server/IP) |
| POST | [`/api/v1/services/{service_ref}/actions`](#op-post-api-v1-services-service-ref-actions) | Execute action |
| POST | [`/api/v1/services/{service_ref}/cancel`](#op-post-api-v1-services-service-ref-cancel) | Cancel service |
| POST | [`/api/v1/services/{service_ref}/change-plan`](#op-post-api-v1-services-service-ref-change-plan) | Change plan |
| GET | [`/api/v1/services/{service_ref}/events`](#op-get-api-v1-services-service-ref-events) | Get events |
| GET | [`/api/v1/services/{service_ref}/upgrade-preview`](#op-get-api-v1-services-service-ref-upgrade-preview) | Preview plan change |

### List services {#op-get-api-v1-services}

`GET /api/v1/services/`

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `status` | query | string | no |  |
| `tag` | query | string | no | Filter by resource tag |
| `resource_type` | query | string | no | Filter by underlying product module. Aliases: cloud_compute→vps, container_apps→container, floating_ip→ip |
| `limit` | query | integer | no | Default: `100`. |
| `offset` | query | integer | no | Default: `0`. |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

A `200` response is a list; each item has these fields:

| Field | Type | Description |
| --- | --- | --- |
| `[].id` | integer |  |
| `[].user_id` | integer |  |
| `[].product_id` | integer |  |
| `[].pricing_id` | integer |  |
| `[].hostname` | string or null |  |
| `[].status` | string |  |
| `[].billing_cycle` | string |  |
| `[].amount` | number |  |
| `[].next_due_date` | string (date-time) or null |  |
| `[].config` | object or null |  |
| `[].module_data` | object or null |  |
| `[].suspension_reason` | string or null |  |
| `[].provisioned_at` | string (date-time) or null |  |
| `[].created_at` | string (date-time) |  |
| `[].updated_at` | string (date-time) |  |
| `[].product_name` | string or null |  |
| `[].product_slug` | string or null |  |
| `[].module_name` | string or null |  |
| `[].pricing_name` | string or null |  |
| `[].termination_date` | string (date-time) or null |  |
| `[].monthly_cap` | number or null |  |
| `[].hourly_usage_this_month` | number or null |  |
| `[].hourly_cap_reached` | boolean or null |  |
| `[].tags` | array of string |  |
| `[].resource` | ServiceResourceSummary or null | Underlying VPS / Container App / Floating IP summary (DB-backed) |
| `[].resource.type` | string | Module resource type: vps \| container \| ip \| external_server |
| `[].resource.label` | string or null | Human label: hostname, server name, or IP/CIDR |
| `[].resource.status` | string or null | Resource-level status (not billing status) |
| `[].resource.address` | string or null | Primary reachability address (IP, IP:port, or CIDR) |
| `[].resource.detail` | Detail | Module-specific fields (uuids, node, plan, attach state, …) |

### Order a platform service, and any add-ons bought with it {#op-post-api-v1-services-order}

`POST /api/v1/services/order`

Order a platform service, and any add-ons bought with it.

The handler checks every add-on before it writes anything. One that the
order cannot take refuses the whole order with 422 and
``{"errors": [<sentence>]}``, and the handler creates nothing:

* the parent decides which add-ons it takes. An instance takes Floating
  IP, DDoS Shield, Mail Hosting and SMTP Relay; a server takes those and
  Snapshot Storage; a floating IP takes DDoS Shield; a mail plan takes
  SMTP Relay. Any other product takes none;
* the add-on product exists and is active. It is not the free plan, and
  it is not a product that goes on a service the customer already has,
  such as an SMTP Relay dedicated IP;
* its price belongs to it, is active and costs more than $0. Without a
  ``pricing_id`` the order takes the price with the parent's billing
  cycle, else the monthly price, else the first active price. An hourly
  price needs the same deposit as an hourly service;
* an order takes one each of DDoS Shield, Mail Hosting, SMTP Relay and
  Snapshot Storage, up to 4 floating IPs for an instance and one /32 for
  a server. Only a floating IP takes a quantity above 1;
* an instance takes floating IPs only with its included IPv4
  (``order_ipv4``), which stays its free primary address;
* DDoS Shield needs an address in the order: the instance's included
  IPv4, a floating IP add-on on a server, or the floating IP being
  ordered. The order names those addresses itself, so its config may not
  set ``ip_service_ids``;
* each add-on's config passes the checks an order of that product on its
  own passes, and its config options change its price the same way. A
  floating IP comes from the parent's location unless it names a pool.

Each add-on becomes a pending service of the parent's owner, with
``addon_of_service_id`` in its config, and the parent's config lists them
in ``addon_service_ids``. When any of them needs a first payment, the
parent, its included IP and the add-ons share one first invoice, and
credit pays it once. Every service then provisions on its own job, queued
when that invoice is paid, or at once when nothing is owed. Once the
services are up, the floating IPs attach to the parent and the Shield
profile protects the addresses. A replayed order answers with the add-ons
it created the first time and creates nothing.

DDoS Shield ordered on its own may name in ``ip_service_ids`` only the
customer's own floating IPs that are not terminated or cancelled.

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Request body

`application/json` (required)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `product_id` | integer | yes |  |
| `pricing_id` | integer | yes |  |
| `hostname` | string or null | no |  |
| `config` | object or null | no |  |
| `idempotency_key` | string or null | no | Replay key for IP orders (double-submit / multi-tab) |
| `turnstile_token` | string or null | no |  |
| `addons` | array of ServiceOrderAddon | no | Up to 8 products bought with this one. Cloud Compute takes Floating IP, DDoS Shield, Mail Hosting and SMTP Relay. Container Apps takes Floating IP, DDoS Shield, Snapshot Storage, Mail Hosting and SMTP Relay. Floating IP takes DDoS Shield. Mail Hosting takes SMTP Relay. When the API cannot take one add-on, it refuses the whole order and creates nothing. |
| `addons[].product_id` | integer | yes | The add-on product. |
| `addons[].pricing_id` | integer or null | no | One of the add-on's active prices. Leave it out to take the price with the parent's billing cycle, else the monthly price, else the first active price. |
| `addons[].quantity` | integer | no | How many to order. Only a floating IP may be more than 1, and each one becomes its own service. |
| `addons[].config` | object or null | no | The add-on's own config, as an order for that product on its own takes it. A floating IP comes from the parent's location unless config.pool_id names a pool. A DDoS Shield add-on takes profile_name (up to 40 characters), protection_mode, default_action, per_source_pps and aggregate_pps. The order sets the addresses it protects, so the API refuses ip_service_ids. |

#### Responses

| Status | Meaning |
| --- | --- |
| `201` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

Fields of a `201` response:

| Field | Type | Description |
| --- | --- | --- |
| `service` | ServiceResponse |  |
| `service.id` | integer |  |
| `service.user_id` | integer |  |
| `service.product_id` | integer |  |
| `service.pricing_id` | integer |  |
| `service.hostname` | string or null |  |
| `service.status` | string |  |
| `service.billing_cycle` | string |  |
| `service.amount` | number |  |
| `service.next_due_date` | string (date-time) or null |  |
| `service.config` | object or null |  |
| `service.module_data` | object or null |  |
| `service.suspension_reason` | string or null |  |
| `service.provisioned_at` | string (date-time) or null |  |
| `service.created_at` | string (date-time) |  |
| `service.updated_at` | string (date-time) |  |
| `service.product_name` | string or null |  |
| `service.product_slug` | string or null |  |
| `service.module_name` | string or null |  |
| `service.pricing_name` | string or null |  |
| `service.termination_date` | string (date-time) or null |  |
| `service.monthly_cap` | number or null |  |
| `service.hourly_usage_this_month` | number or null |  |
| `service.hourly_cap_reached` | boolean or null |  |
| `service.tags` | array of string |  |
| `service.resource` | ServiceResourceSummary or null | Underlying VPS / Container App / Floating IP summary (DB-backed) |
| `invoice_id` | integer or null |  |
| `requires_payment` | boolean |  |
| `checkout_available` | boolean |  |
| `amount_due` | string or null |  |
| `message` | string or null |  |
| `addons` | array of ServiceResponse | The services created for the order's add-ons, in the order the client listed them, one per unit of quantity. A replayed order lists the add-ons it created the first time. |
| `addons[].id` | integer |  |
| `addons[].user_id` | integer |  |
| `addons[].product_id` | integer |  |
| `addons[].pricing_id` | integer |  |
| `addons[].hostname` | string or null |  |
| `addons[].status` | string |  |
| `addons[].billing_cycle` | string |  |
| `addons[].amount` | number |  |
| `addons[].next_due_date` | string (date-time) or null |  |
| `addons[].config` | object or null |  |
| `addons[].module_data` | object or null |  |
| `addons[].suspension_reason` | string or null |  |
| `addons[].provisioned_at` | string (date-time) or null |  |
| `addons[].created_at` | string (date-time) |  |
| `addons[].updated_at` | string (date-time) |  |
| `addons[].product_name` | string or null |  |
| `addons[].product_slug` | string or null |  |
| `addons[].module_name` | string or null |  |
| `addons[].pricing_name` | string or null |  |
| `addons[].termination_date` | string (date-time) or null |  |
| `addons[].monthly_cap` | number or null |  |
| `addons[].hourly_usage_this_month` | number or null |  |
| `addons[].hourly_cap_reached` | boolean or null |  |
| `addons[].tags` | array of string |  |
| `addons[].resource` | ServiceResourceSummary or null | Underlying VPS / Container App / Floating IP summary (DB-backed) |

### Fetch a service by numeric id OR underlying resource UUID (instance/server/IP) {#op-get-api-v1-services-service-ref}

`GET /api/v1/services/{service_ref}`

Fetch a service by numeric id OR underlying resource UUID (instance/server/IP).

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Parameters

| Name | In | Type | Required |
| --- | --- | --- | --- |
| `service_ref` | path | string | yes |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

Fields of a `200` response:

| Field | Type | Description |
| --- | --- | --- |
| `id` | integer |  |
| `user_id` | integer |  |
| `product_id` | integer |  |
| `pricing_id` | integer |  |
| `hostname` | string or null |  |
| `status` | string |  |
| `billing_cycle` | string |  |
| `amount` | number |  |
| `next_due_date` | string (date-time) or null |  |
| `config` | object or null |  |
| `module_data` | object or null |  |
| `suspension_reason` | string or null |  |
| `provisioned_at` | string (date-time) or null |  |
| `created_at` | string (date-time) |  |
| `updated_at` | string (date-time) |  |
| `product_name` | string or null |  |
| `product_slug` | string or null |  |
| `module_name` | string or null |  |
| `pricing_name` | string or null |  |
| `termination_date` | string (date-time) or null |  |
| `monthly_cap` | number or null |  |
| `hourly_usage_this_month` | number or null |  |
| `hourly_cap_reached` | boolean or null |  |
| `tags` | array of string |  |
| `resource` | ServiceResourceSummary or null | Underlying VPS / Container App / Floating IP summary (DB-backed) |
| `resource.type` | string | Module resource type: vps \| container \| ip \| external_server |
| `resource.label` | string or null | Human label: hostname, server name, or IP/CIDR |
| `resource.status` | string or null | Resource-level status (not billing status) |
| `resource.address` | string or null | Primary reachability address (IP, IP:port, or CIDR) |
| `resource.detail` | Detail | Module-specific fields (uuids, node, plan, attach state, …) |

### Execute action {#op-post-api-v1-services-service-ref-actions}

`POST /api/v1/services/{service_ref}/actions`

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Parameters

| Name | In | Type | Required |
| --- | --- | --- | --- |
| `service_ref` | path | string | yes |

#### Request body

`application/json` (required)

| Field | Type | Required |
| --- | --- | --- |
| `action` | string | yes |
| `params` | object or null | no |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

### Cancel service {#op-post-api-v1-services-service-ref-cancel}

`POST /api/v1/services/{service_ref}/cancel`

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Parameters

| Name | In | Type | Required |
| --- | --- | --- | --- |
| `service_ref` | path | string | yes |

#### Request body

`application/json` (required)

| Field | Type | Required |
| --- | --- | --- |
| `reason` | string or null | no |
| `immediate` | boolean | no |
| `keep_snapshot` | boolean | no |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

### Change plan {#op-post-api-v1-services-service-ref-change-plan}

`POST /api/v1/services/{service_ref}/change-plan`

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Parameters

| Name | In | Type | Required |
| --- | --- | --- | --- |
| `service_ref` | path | string | yes |

#### Request body

`application/json` (required)

| Field | Type | Required |
| --- | --- | --- |
| `new_pricing_id` | integer | yes |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

Fields of a `200` response:

| Field | Type |
| --- | --- |
| `change_type` | string |
| `net_amount` | number |
| `credit_amount` | number |
| `charge_amount` | number |
| `credit_issued` | number |
| `days_remaining` | integer |
| `invoice_id` | integer or null |
| `status` | string or null |
| `message` | string or null |

### Get events {#op-get-api-v1-services-service-ref-events}

`GET /api/v1/services/{service_ref}/events`

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `service_ref` | path | string | yes |  |
| `page` | query | integer | no | Default: `1`. |
| `limit` | query | integer | no | Default: `50`. |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

A `200` response is a list; each item has these fields:

| Field | Type |
| --- | --- |
| `[].id` | integer |
| `[].event_type` | string |
| `[].actor_type` | string |
| `[].actor_id` | integer or null |
| `[].description` | string or null |
| `[].metadata` | object or null |
| `[].created_at` | string (date-time) |

### Preview plan change {#op-get-api-v1-services-service-ref-upgrade-preview}

`GET /api/v1/services/{service_ref}/upgrade-preview`

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Parameters

| Name | In | Type | Required |
| --- | --- | --- | --- |
| `service_ref` | path | string | yes |
| `new_pricing_id` | query | integer | yes |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

Fields of a `200` response:

| Field | Type |
| --- | --- |
| `credit_amount` | number |
| `charge_amount` | number |
| `net_amount` | number |
| `credit_estimate` | number |
| `creditable_days` | integer |
| `days_remaining` | integer |
| `days_in_cycle` | integer |
| `change_type` | string |
