Files
docs-v2/content/docs/manage/common-api-routes.mdx
T
2026-10-28 16:45:08 -04:00

401 lines
14 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: "Common API Routes"
description: "API routes and patterns for the most common actions and flows"
---
All routes in this guide require an API key. See [Integration API](/manage/integration-api) for creating and using API keys (Bearer token in the `Authorization` header). Many routes use an organization ID in the path — see [Organization ID](/manage/organizations/org-id) for where to find it.
<Note>
This guide is a **minimal** walkthrough for creating core Pangolin components (sites, resources, targets) via the API. It is not exhaustive — some elements are omitted for simplicity — but everything shown here works and illustrates patterns used elsewhere in the API. For full coverage of endpoints (get, update, delete, list, etc.), use the [Swagger docs](https://api.pangolin.net/v1/docs).
</Note>
OpenAPI-compliant documentation is available at https://api.pangolin.net/v1/openapi.json and https://api.pangolin.net/v1/openapi.yaml
## What is an identifier?
In the Pangolin app, sites, public resources, private resources, and clients have an **Identifier**. In the API, this value is called `niceId`.
The identifier is a human-readable, organization-unique value you can use when you want something more stable and memorable than a numeric ID. For example, a site might have a numeric `siteId` like `8723` and a `niceId` like `warehouse-west`.
Identifiers are especially useful for automation:
- In [Blueprints](/manage/blueprints), resource and site keys act as stable identifiers. A target can reference a site by its identifier, such as `site: warehouse-west`.
- In the API, responses include both the numeric ID and `niceId` when the object supports identifiers. Some routes can also look up objects by `niceId`, such as `GET /org/{orgId}/site/{niceId}` for sites.
Use the numeric ID when an endpoint asks for `siteId`, `resourceId`, or `siteResourceId`. Use `niceId` when an endpoint or blueprint field asks for an identifier.
## Create site
This section assumes you're creating a **Pangolin Site**. For all Site endpoints, see [Site API (Swagger)](https://api.pangolin.net/v1/docs/#/Site).
**PUT** `/org/{orgId}/site`
**Path**
- `orgId` (string) — organization ID
**Body (Newt)**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | Yes | Display name for the site |
| `type` | string | Yes | Use `"newt"` |
| `niceId` | string | No | Identifier for the site. Must be unique in the org. Generated if omitted. |
If you omit them, Pangolin generates the Newt credentials (`newtId` and `secret`) and the site's address. Only the create response includes the secret, so save it then.
**Example Response**
```json
{
"data": {
"siteId": 8723,
"niceId": "quiet-lerista-labialis",
"name": "My Site",
"type": "newt",
"online": false,
"address": "100.90.128.0/24",
"newtId": "se897rmyjvamy5q",
"secret": "ystsahnvqetgx1fhn2udylq0zqgv611zbfp6ya3jvfrprn7q"
},
"success": true,
"error": false,
"message": "Site created successfully",
"status": 201
}
```
### Advanced option: pick-site-defaults
Use this when you want to generate some template values and control the site creation.
**GET** `/org/{orgId}/pick-site-defaults`
Returns values you pass into the create-site endpoint. The response also includes exit node fields (`exitNodeId`, `subnet`, `endpoint`, etc.). Newt sites don't use them because Newt picks its exit node when it connects.
**Path**
- `orgId` (string) — organization ID
**Example Response**
```json
{
"data": {
"newtId": "jwhk5154mfmos0s",
"newtSecret": "8afipi4i79jjbsxjqpkgc0xe2ge143s54oi64mw5567mxgr8",
"clientAddress": "100.90.128.0"
},
"success": true,
"error": false,
"message": "Site defaults chosen successfully",
"status": 200
}
```
### Create site
**PUT** `/org/{orgId}/site`
**Path**
- `orgId` (string) — organization ID
**Body (Newt)**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | Yes | Display name for the site |
| `type` | string | Yes | Use `"newt"` |
| `address` | string | No | From pick-site-defaults `clientAddress`. Must be an IP inside the org subnet that no other site or client uses. |
| `newtId` | string | No | From pick-site-defaults `newtId` |
| `secret` | string | No | From pick-site-defaults `newtSecret` |
| `niceId` | string | No | Identifier for the site. Must be unique in the org. |
**Example Response**
```json
{
"data": {
"siteId": 8723,
"niceId": "quiet-lerista-labialis",
"name": "My Site",
"type": "newt",
"online": false,
"address": "100.90.128.0/24",
"newtId": "se897rmyjvamy5q",
"secret": "ystsahnvqetgx1fhn2udylq0zqgv611zbfp6ya3jvfrprn7q"
},
"success": true,
"error": false,
"message": "Site created successfully",
"status": 201
}
```
## Create public HTTP resource
You need a **domain ID** before creating a resource. List your org's domains, then create the resource with the chosen domain. For all public resource endpoints, see [Public Resource API (Swagger)](https://api.pangolin.net/v1/docs/#/Public%20Resource).
<Steps>
<Step title="List domains">
Call the list-domains endpoint to get available domains and their `domainId` values.
</Step>
<Step title="Create the resource">
Call the create-resource endpoint with `mode: "http"` and the `domainId` from step 1.
</Step>
<Step title="Add targets to the resource">
Call the create-target endpoint for each backend (site + ip:port) that should serve traffic for the resource.
</Step>
</Steps>
### List domains
**GET** `/org/{orgId}/domains`
Returns all domains for the organization. Use `domainId` from a domain when creating a resource.
**Path**
- `orgId` (string) — organization ID
**Query** (optional)
| Param | Type | Default | Description |
|-------|------|---------|-------------|
| `limit` | number | 1000 | Max domains to return |
| `offset` | number | 0 | Pagination offset |
**Example Response**
```json
{
"data": {
"domains": [
{
"domainId": "pg3i1k4lhibhl3i",
"baseDomain": "pangolin.net",
"verified": true,
"type": "ns"
},
{
"domainId": "q1ngj5341k7oydo",
"baseDomain": "bitwarden.pangolin.net",
"verified": true,
"type": "cname"
}
],
"pagination": {
"total": 2,
"limit": 1000,
"offset": 0
}
},
"success": true,
"error": false,
"message": "Domains retrieved successfully",
"status": 200
}
```
### Create public HTTP resource
**PUT** `/org/{orgId}/public-resource`
**Path**
- `orgId` (string) — organization ID
**Body (HTTP resource)**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | Yes | Display name for the resource (1–255 chars) |
| `mode` | string | Yes | Use `"http"` for a public HTTP resource |
| `domainId` | string | Yes | From list-domains |
| `subdomain` | string \| null | No | See below. |
<Note>
Older integrations sent `http: true` and `protocol: "tcp"` instead of `mode`. The API still accepts those fields, but they're deprecated. Use `mode` instead.
</Note>
**Subdomain and domain types**
Domains come in three types: **ns** | **cname** | **wildcard**.
<Note>
**Wildcard** is only available in self-hosted Pangolin. Pangolin Cloud uses **ns** and **cname** only.
</Note>
- **ns** — You can use the base domain (set `subdomain` to `null`) or set a subdomain (e.g. `my-app` → `my-app.digpangolin.io`).
- **cname** — Only the base domain is used; set `subdomain` to `null` (the domain’s `baseDomain` is the FQDN).
- **wildcard** — Same as ns for subdomain behavior (self-hosted only).
The `subdomain` value is combined with the base domain from `domainId` to form the FQDN. Omit `subdomain` or pass `null` when using the base domain alone.
**Example request**
```json
{
"name": "My Resource",
"mode": "http",
"subdomain": "my-subdomain",
"domainId": "pg3i1k4lhibhl3i"
}
```
**Example Response**
```json
{
"data": {
"resourceId": 9942,
"niceId": "decent-louisiana-waterthrush",
"name": "My Resource",
"subdomain": "my-subdomain",
"fullDomain": "my-subdomain.pangolin.net",
"domainId": "pg3i1k4lhibhl3i"
},
"success": true,
"error": false,
"message": "Http resource created successfully",
"status": 201
}
```
### Add targets to the resource
**PUT** `/public-resource/{resourceId}/target`
Add a target (backend) to a resource. Use the numeric `resourceId` from the create-resource response. The target is the site and address (ip + port) that will receive traffic for the resource. For all target endpoints, see [Resource Target API (Swagger)](https://api.pangolin.net/v1/docs/#/Resource%20Target).
**Path**
- `resourceId` (number) — From create-resource response (e.g. `9942`)
**Body**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `siteId` | number | Yes | Site that hosts the backend (from create-site or list sites) |
| `ip` | string | Yes | Backend IP or hostname |
| `port` | number | Yes | Backend port |
| `method` | string | Yes (HTTP) | Scheme Pangolin uses to reach the backend: `"http"`, `"https"`, or `"h2c"`. HTTP resources need it; Pangolin skips HTTP targets that have no method. |
**Example request**
```json
{
"ip": "localhost",
"port": 8080,
"method": "http",
"siteId": 8723
}
```
**Example response**
```json
{
"data": {
"targetId": 11280,
"resourceId": 9942,
"siteId": 8723,
"ip": "localhost",
"method": "http",
"port": 8080
},
"success": true,
"error": false,
"message": "Target created successfully",
"status": 201
}
```
## Create private resource
You need an existing site. For more endpoints, see [Private Resource API (Swagger)](https://api.pangolin.net/v1/docs/#/Private%20Resource).
### Create private resource
**PUT** `/org/{orgId}/private-resource`
**Path**
- `orgId` (string) — organization ID
**Body**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | Yes | Display name (1–255 chars) |
| `siteIds` | number[] | Yes | Sites that can reach the resource (from create-site or list sites) |
| `mode` | string | Yes | `"host"` \| `"cidr"`. This guide covers these two; see Swagger for `http`, `ssh`, `inference`, and `gateway`. |
| `destination` | string | Yes | For `host`: IPv4 address or hostname (e.g. `localhost`). For `cidr`: CIDR (e.g. `10.0.0.0/24`). |
| `alias` | string | For host+domain | Alias as a fully qualified domain name (e.g. `private-resource.internal`). Wildcards are allowed (e.g. `*.example.internal`). Required when `destination` is a hostname; optional for an IP. Must be unique in the org. |
| `roleIds` | number[] | Yes | Role IDs that can access. Pass `[]` for none. |
| `userIds` | string[] | Yes | User IDs that can access. Pass `[]` for none. |
| `clientIds` | number[] | Yes | Machine client IDs that can access. Pass `[]` for none. |
| `tcpPortRangeString` | string | No | See below. Default `"*"`. |
| `udpPortRangeString` | string | No | See below. Default `"*"`. |
| `disableIcmp` | boolean | No | Default `false` |
| `niceId` | string | No | Identifier for the resource. Generated if omitted. |
<Note>
The older `siteId` (single number) field still works and is merged into `siteIds`, but it's deprecated.
</Note>
**TCP/UDP port range strings:** Control which ports are allowed for the private resource.
- **`"*"`** — Allow all ports (common for TCP when you want full access to the host).
- **`""`** (empty string) — Allow no ports. Use when you don’t need that protocol (e.g. `udpPortRangeString: ""` if only TCP is used).
- **Specific ports or ranges** — Comma-separated list: single ports (e.g. `"80,443"`) or ranges (e.g. `"8000-9000"`). Example: `"80,443,8080-8090"` allows 80, 443, and 8080–8090.
Use `tcpPortRangeString` and `udpPortRangeString` independently (e.g. TCP all, UDP none, or vice versa).
The org Admin role always gets access. The roles, users, and clients you list get access in addition to it.
**Example request**
```json
{
"name": "My Private Resource",
"siteIds": [8723],
"mode": "host",
"destination": "localhost",
"alias": "private-resource.internal",
"tcpPortRangeString": "*",
"udpPortRangeString": "",
"disableIcmp": false,
"roleIds": [],
"userIds": [],
"clientIds": []
}
```
**Example response**
```json
{
"data": {
"siteResourceId": 1165,
"networkId": 412,
"niceId": "unsung-round-tailed-ground-squirrel",
"name": "My Private Resource",
"mode": "host",
"destination": "localhost",
"alias": "private-resource.internal",
"tcpPortRangeString": "*",
"udpPortRangeString": "",
"disableIcmp": false
},
"success": true,
"error": false,
"message": "Site resource created successfully",
"status": 201
}
```
## Assign users and roles to a resource (public or private)
You can grant access to a **public resource** or a **private resource** by adding roles or users. First list roles and users in the org to get IDs, then call the add endpoints. The Admin role cannot be assigned via these endpoints.
### Get role and user IDs
**GET** `/org/{orgId}/roles` returns `data.roles`, the roles in the org. Use a role's `roleId` (number) when adding it to a resource. Optional query parameters: `page` (default `1`), `pageSize` (default `20`), and `query` (filter by name).
**GET** `/org/{orgId}/users` returns `data.users`, the users in the org. Use a user's `id` (string) as `userId` when adding them to a resource. Optional query parameters: `page` (default `1`), `pageSize` (default `20`), and `query` (search).
### Public resource
**POST** `/public-resource/{resourceId}/roles/add` — **Path:** `resourceId` (number, from create public-resource). **Body:** `{ "roleId": number }`. Admin role not allowed.
**POST** `/public-resource/{resourceId}/users/add` — **Path:** `resourceId` (number). **Body:** `{ "userId": string }`.
Both return `{ "data": {}, "success": true, "error": false, "message": "...", "status": 201 }`.
### Private resource
**POST** `/private-resource/{siteResourceId}/roles/add` — **Path:** `siteResourceId` (number, from create private-resource). **Body:** `{ "roleId": number }`. Admin role not allowed.
**POST** `/private-resource/{siteResourceId}/users/add` — **Path:** `siteResourceId` (number). **Body:** `{ "userId": string }`.
Same response shape as above. Role must belong to the same org as the private resource. For more endpoints (list/remove), see [Public Resource API](https://api.pangolin.net/v1/docs/#/Public%20Resource) and [Private Resource API](https://api.pangolin.net/v1/docs/#/Private%20Resource).