# Targets API endpoints

> Cloider API reference for targets, route a domain path over HTTPS to an app, a Kubernetes service or a bucket website, or a custom domain to a LoadBalancer, list, change and delete them.

A **target** routes requests for a domain and path, such as `https://example.com/` or `https://example.com/api`, to an [app](/apps/overview), to a Service in one of your [Kubernetes clusters](/kubernetes/overview), or to a bucket's [static website](/object-storage/static-websites). Cloider sets up the routing and the HTTPS certificate for you. A `LoadBalancer` target instead gives a custom domain an A record to a [LoadBalancer](/kubernetes/load-balancers)'s IP, see [Point a domain at a LoadBalancer](/domains/targets#point-a-domain-at-a-loadbalancer). These endpoints list, create, change and delete targets.

All paths are relative to `https://cloider.com/api`. See [Targets](/domains/targets) for how routing works.

## At a glance

| Method | Path | Access |
| --- | --- | --- |
| GET | [`/targets?domain_name=`](#list-a-domains-targets) | Read |
| POST | [`/targets`](#create-a-target) | Write |
| PUT | [`/targets/:hashed_id`](#change-a-target) | Write |
| DELETE | [`/targets/:hashed_id`](#delete-a-target) | Write |

## The target object

```json
{
  "hashed_id": "x4v7n",
  "domain_name": "example.com",
  "target_type": "Cluster",
  "cluster_id": "p8w2d",
  "cluster_name": "production",
  "app_id": "",
  "app_name": "",
  "app_label": "",
  "bucket_id": "",
  "bucket_name": "",
  "loadbalancer_ip": "",
  "namespace": "shop",
  "service": "api",
  "port": 8080,
  "protocol": "TCP",
  "path": "/api",
  "target_path": "/",
  "created_at": "2026-10-03T17:41:20.551093Z"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `hashed_id` | string | The target's id |
| `domain_name` | string | The domain it routes |
| `target_type` | string | `App`, `Bucket`, `Cluster` or `LoadBalancer` |
| `cluster_id`, `cluster_name` | string | For a `Cluster` or `LoadBalancer` target, the cluster's id and name |
| `app_id`, `app_name`, `app_label` | string | For an `App` target, the app's id, name and label |
| `bucket_id`, `bucket_name` | string | For a `Bucket` target, the bucket's id and name |
| `loadbalancer_ip` | string | For a `LoadBalancer` target, the IP the domain's A record points at. Empty while the LoadBalancer has none, when the domain has no A record |
| `namespace`, `service`, `port`, `protocol` | string, string, integer, string | For a `Cluster` target, the Kubernetes Service in your cluster that requests go to. For an `App` target, `namespace` and `service` are empty, and `port` is the app's port. For a `Bucket` target, `namespace` and `service` are empty too. For a `LoadBalancer` target, `namespace` and `service` name the LoadBalancer, `port` is `0` and `protocol` is empty |
| `path` | string | The path on the domain that is routed, for example `/` or `/api`. Always `/` for a `Bucket` or `LoadBalancer` target |
| `target_path` | string | The path requests are sent to on the service. Defaults to `path` |
| `created_at` | string | When the target was created |

A domain can route each `path` to one target. The first target on a domain makes its status `Active`.

## List a domain's targets

```http
GET /targets?domain_name=:domain_name
```

Returns the targets of one domain, newest first.

**Access:** any token.

| Query parameter | Required | Description |
| --- | --- | --- |
| `domain_name` | Yes | The domain, for example `example.com` |

```bash
curl "https://cloider.com/api/targets?domain_name=example.com" \
  -H "Authorization: Bearer $CLOIDER_TOKEN"
```

The response is a list of [target objects](#the-target-object). Without `domain_name` the answer is `400 Domain name is required`; for a domain that isn't the account's, `403 You don't have permission to access targets for this domain`.

## Create a target

```http
POST /targets
```

Routes a domain path to an app, a cluster service or a bucket's website. The route and its certificate are set up in the background after the request returns.

**Access:** Read & Write token.

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `domain_name` | string | Yes | A domain of the account |
| `target_type` | string | No | `App`, `Bucket`, `Cluster` or `LoadBalancer`. Default `Cluster` |
| `path` | string | No | The path on the domain. Default `/`. Must start with `/` and use only letters, digits, `/`, `_`, `.` and `-`. Must be `/` for `Bucket` and `LoadBalancer` |
| `target_path` | string | No | The path on the service. Default: the same as `path`. Same rules |
| `app_id` | string | For `App` | The app's `hashed_id`. The app must be `Ready` or `Paused` |
| `bucket_id` | string | For `Bucket` | The bucket's `hashed_id`. The bucket must be `Ready`, with its [static website](/object-storage/static-websites) on |
| `cluster_id` | string | For `Cluster` and `LoadBalancer` | The cluster's `hashed_id`. For `LoadBalancer`, the cluster must be `Ready` or `Paused` |
| `cluster_name` | string | For `Cluster` | The cluster's `cluster_name`, exactly as in `GET /clusters` |
| `namespace` | string | For `Cluster` and `LoadBalancer` | The Service's namespace |
| `service` | string | For `Cluster` and `LoadBalancer` | The Service's name. For `LoadBalancer`, a Service of type `LoadBalancer` that has an IP |
| `port` | integer | For `Cluster` | The Service port |
| `protocol` | string | No | The port's protocol as listed by `GET /clusters/services`, for example `TCP` |

`GET /clusters/services` lists every Service with its namespace, ports, `type` and, for a LoadBalancer, its `ip`; see [Clusters](/api/endpoints/clusters#list-services-in-your-clusters).

**Route to an app**

```bash
curl -X POST https://cloider.com/api/targets \
  -H "Authorization: Bearer $CLOIDER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domain_name": "blog.example.com", "target_type": "App", "app_id": "a8q2w", "path": "/"}'
```

**Route to a bucket website**

```bash
curl -X POST https://cloider.com/api/targets \
  -H "Authorization: Bearer $CLOIDER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domain_name": "docs.example.com", "target_type": "Bucket", "bucket_id": "s3w8q"}'
```

**Point at a LoadBalancer**

```bash
curl -X POST https://cloider.com/api/targets \
  -H "Authorization: Bearer $CLOIDER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domain_name": "play.example.com", "target_type": "LoadBalancer", "cluster_id": "p8w2d", "namespace": "games", "service": "server"}'
```

The domain must be a custom domain with no other targets. Its A record is created with the target and deleted with it. Cloider issues no certificate for the domain: TLS, and whatever routes requests behind the LoadBalancer, are yours to manage.

**Route to a cluster service**

```bash
curl -X POST https://cloider.com/api/targets \
  -H "Authorization: Bearer $CLOIDER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "domain_name": "example.com",
    "target_type": "Cluster",
    "cluster_id": "p8w2d",
    "cluster_name": "production",
    "namespace": "shop",
    "service": "api",
    "port": 8080,
    "protocol": "TCP",
    "path": "/api",
    "target_path": "/"
  }'
```

```json title="Response"
{"hashed_id": "x4v7n", "domain_name": "example.com", "message": "Target created successfully"}
```

For a custom domain, the domain's nameservers must point to Cloider for the route to be reachable; see [Expose services](/kubernetes/expose-services) and [Domains for apps](/apps/domains).

### Errors

| Status | Field | Message |
| --- | --- | --- |
| `400` | `domain_name` | `Domain name is required` |
| `400` | `path`, `target_path` | `Path must start with / and may only contain letters, numbers and / _ . -`, `Target path must start with / and may only contain letters, numbers and / _ . -` |
| `400` | `app_id` | `App is required` |
| `400` | `bucket_id` | `Bucket is required` |
| `400` | `path`, `target_path` | `A bucket website is served at the root of its domain, the path must be /`, `A bucket website is served at the root of its domain, the target path must be /` (Bucket targets) |
| `400` | `service`, `namespace`, `port` | `Service is required`, `Namespace is required`, `Valid port is required` |
| `400` | `cluster_id`, `service`, `path`, `target_path` | `Cluster is required`, `LoadBalancer is required`, `A LoadBalancer takes the whole domain, the path must be /`, `A LoadBalancer takes the whole domain, the target path must be /` (LoadBalancer targets) |
| `400` | `_` | `A LoadBalancer target needs a custom domain.`, `A LoadBalancer target takes the whole domain. Delete the domain's other targets first, or use another domain.`, `This domain points at a LoadBalancer, which takes the whole domain. Delete that target first, or use another domain.` |
| `400` | `_` | `This cluster is not ready yet. Wait until it is running, then add the target.`, `This LoadBalancer has no IP yet. Wait until it gets one, then add the target.`, `The domain has an A, AAAA, ALIAS or CNAME record of its own. Delete it from the domain's DNS records first, then add the target.` |
| `400` | `_` | `Invalid target type specified` |
| `400` | `_` | `This path is already in use on this domain. Please choose a different path.` |
| `400` | `_` | `This app is not ready yet. Wait until it finishes deploying, then add the target.` |
| `400` | `_` | `This bucket is not ready yet. Wait until it is created, then add the target.`, `Turn on the bucket's website first, from its Settings.` |
| `400` | `_` | `Service and namespace names are too long for this cluster (combined limit exceeded)`, `This app's name is too long to be exposed on a domain.` |
| `400` | `_` | `Your account is suspended. Upgrade to a paid plan to continue using our services.` |
| `403` | `_` | `This API token is read only!` |
| `404` | `_` | `App not found!`, `Bucket not found!`, `LoadBalancer not found! ...`, or `Not Found` (plain text) for a domain or cluster that isn't the account's |

## Change a target

```http
PUT /targets/:hashed_id
```

Changes where a target routes: its domain, path, target path, and the app, service or bucket. The body is the same as for [creating a target](#request-body), and every field is applied, so send the complete target.

**Access:** Read & Write token.

```bash
curl -X PUT https://cloider.com/api/targets/x4v7n \
  -H "Authorization: Bearer $CLOIDER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "domain_name": "example.com",
    "target_type": "Cluster",
    "cluster_id": "p8w2d",
    "cluster_name": "production",
    "namespace": "shop",
    "service": "api-v2",
    "port": 8080,
    "protocol": "TCP",
    "path": "/api",
    "target_path": "/"
  }'
```

```json title="Response"
{"hashed_id": "x4v7n", "domain_name": "example.com", "message": "Target updated successfully"}
```

The errors are those of creating a target, and `Operation failed, Couldn't update the target!`.

## Delete a target

```http
DELETE /targets/:hashed_id
```

Removes the route. The app, service or bucket website keeps running but is no longer reachable on that domain and path. When a domain loses its last target, its status becomes `Suspended`.

**Access:** Read & Write token.

```bash
curl -X DELETE https://cloider.com/api/targets/x4v7n \
  -H "Authorization: Bearer $CLOIDER_TOKEN"
```

```json title="Response"
{"message": "Target deleted successfully"}
```

`403` for read only tokens and Restricted members, `404 Not Found` (plain text) for an unknown target.

## Related

- [Targets](/domains/targets) and [HTTPS certificates](/domains/https-certificates)
- [Static websites](/object-storage/static-websites) and the [website endpoints](/api/endpoints/buckets#get-a-buckets-website) of buckets
- [Expose services](/kubernetes/expose-services) from a cluster
- [cloider target](/cli/commands/target) and the [cloider_target](/terraform/resources/target) Terraform resource
