---
title: API reference
description: "Reference for the OpenLM LicOps admission endpoints in OpenLM Platform: request and response fields, deny reasons, and operating endpoints."
product: OpenLM Platform
---

Use this page when you write an integration, or when you have a LicOps response and want to know what it means. The other pages in this section show each request in the task it belongs to.

## The API browser on every LicOps host

Every LicOps deployment serves an interactive reference, generated from the running build, at:

```text
http://<licops-host>:8100/swagger
```

Use it for the exact schemas of your build. Most of what it lists is for operating the Router. The admission endpoints in the following section are the ones integrations call.

:::warning[Restrict access to ports 8100 and 8101]

Ports `8100` and `8101` are administration interfaces. Allow access to them only from your administration network and from the hosts that submit jobs, and do not publish them to the internet. Publish only the license ports your clients use. See [System requirements](https://www.openlm.com/documentation/cloud/deployment-operations/licops/system-requirements).

:::

## Admission endpoints

Base path: `http://<licops-host>:8100/api/v1/admission-controller`. Requests and responses are JSON, field names are camelCase, and values such as `decision` are strings. The following table lists the endpoints:

| Method | Path | Success | Other answers |
| --- | --- | --- | --- |
| `POST` | `/admit` | `200` with a decision | `400` for an invalid body, or for a session ID reused with a different vendor, feature, or seat count. `503` during cold start, with `Retry-After`. |
| `POST` | `/dispatch` | `200` with a license server | `400` for an invalid body. `404` for an unknown session. `503` with `ThrottleDeferred` and `Retry-After`, or with `NoCapacityAtDispatch`. |
| `POST` | `/release` | `200`, always | `400` for an invalid body. |
| `GET` | `/availability` | `200`, always | None |

### Admit

The following table lists the fields of the request:

| Field | Required | Description |
| --- | --- | --- |
| `sessionId` | Yes | An ID you generate, normally a UUID. Use it for all three requests of one job. A repeated admit with the same ID returns the original answer while the session is valid. A denied admit is not stored. |
| `vendor` | Yes | The pool's vendor daemon name. Exact match, including case. |
| `feature` | Yes | The pool's feature name. Exact match, including case. Send spaces as spaces. |
| `user` | No | Who submitted the job. Recorded, and available to admission rules. |
| `host` | No | Where the job was submitted from. Recorded, and available to admission rules as **client host**. |
| `totalQuantity` | No | Seats to admit. At least `1`. The default is `1`. All of them come from one server. |
| `jobDetails` | No | Job context recorded with the seat: `clusterName`, `account`, `queue`, `jobName`, `declaredRuntimeSeconds`, and on Kubernetes `namespace` and `podName`. Each field is optional. It never changes the decision. |

The response:

| Field | Description |
| --- | --- |
| `decision` | `Admit` or `Deny`. |
| `sessionId` | Your session ID on `Admit`, or `null` on `Deny`. |
| `expiresAt` | On `Admit`, when the seat lapses if the job is not dispatched by then. |
| `reason` | On `Deny`, why. See [Deny reasons](#deny-reasons). |

A `503` during cold start carries a problem document with `type`, `title`, `status`, and `traceId`, and a `Retry-After` header.

### Deny reasons

The following table lists the deny reasons, what happened, and what to do:

| Reason | What happened | What to do |
| --- | --- | --- |
| `NoServerAvailable` | No healthy server serves this vendor and feature. | Check the spelling against `GET /api/v1/stats` first. Then check whether the pool's servers are down. |
| `NoCapacity` | The servers are reachable, but none has enough free seats for the request. | Queue the job and try again later. |
| `ConstraintBlocked` | Servers have free seats, but every one of them is capped by a reservation or blocked during the current period. | Wait for seats under the cap to free up, or change the reservation. See [Cap seats on a schedule](https://www.openlm.com/documentation/cloud/deployment-operations/licops/reservations). |
| `PolicyDenied` | An admission rule with **Deny the request** matched. | Intended. Check the rules under **LicOps Configuration**. |
| `StaleSnapshot` | LicOps has not received fresh figures from OpenLM Platform, so its counts are not reliable. | Restore the OpenLM Platform sync. Retrying does not help until then. See [Troubleshooting](https://www.openlm.com/documentation/cloud/deployment-operations/licops/troubleshooting#why-is-licops-not-syncing-with-openlm-platform). |
| `ContentionExhausted` | No longer returned. | Nothing. The value remains for compatibility with older clients. |

### Dispatch

The following table lists the fields of the request:

| Field | Required | Description |
| --- | --- | --- |
| `sessionId` | Yes | The ID you sent to admit. |
| `executionNodeIp` | Yes | The IP address of the node that runs the job. With checkout routing, LicOps uses it to match the node's checkouts to this session. |
| `jobId` | No | Your scheduler's job ID. Recorded. |
| `jobDetails` | No | As for admit. A value here replaces the admit value field by field. Leaving it out keeps the admit values. |

The response:

| Field | Description |
| --- | --- |
| `hostName`, `port` | The license server to check out from. For a three-server redundant license server, its first machine. |
| `licenseFile` | The value to use for `LM_LICENSE_FILE`, as `port@host`. For a three-server redundant license server, all three machines, comma-separated. Use it exactly as returned. |
| `fallbackTargets` | The pool's other healthy servers, each with its own `hostName`, `port`, and `licenseFile`. |

A `404` has an empty body: the session does not exist or has ended. A `503` has a plain-text body: `ThrottleDeferred` when a Throttle rule is pacing checkouts, or `NoCapacityAtDispatch` when the session's server failed and no other server in the pool has a free seat. After `ThrottleDeferred`, the seat is still held: wait for `Retry-After` and dispatch again. After `NoCapacityAtDispatch`, LicOps has released the session and sends no `Retry-After`: admit again with a new session ID.

### Release

The request takes only `sessionId`. The response always has status `200`, and its `state` is one of:

| State | Meaning |
| --- | --- |
| `Released` | This request returned the seat. |
| `AlreadyClosed` | The session had already ended. Nothing to do. |
| `NotFound` | No such session exists. Nothing to do. |

### Availability

`GET /availability` returns every pool LicOps knows and every server behind it, including servers that are down, with the figures admission itself uses. It changes nothing.

| Field | Description |
| --- | --- |
| `lastPlatformSyncTime`, `lastReconcileTime` | When LicOps last received figures from OpenLM Platform, and last corrected its counts with them. |
| `coldStart` | `true` until LicOps has its first figures. Admit answers `503` meanwhile. |
| `staleForAdmission` | `true` when the figures are too old to trust. Admit denies with `StaleSnapshot` meanwhile. |
| `pools[]` | One entry per pool, with `vendor`, `feature`, the totals `total`, `used`, `reserved`, and `free` over the servers admission can use, and `largestAdmittableRequest`: the most seats one request could get right now. |
| `pools[].servers[]` | One entry per server, with `isHealthy`, the seat counts, and `constraints`. |
| `pools[].servers[].constraints[]` | Each reservation cap on the server: its `limit`, the seats `held` under it, and the `headroom` left. A `limit` of `9223372036854775807` means no cap is in force. |

`free` is not rounded up to zero. A negative value means LicOps admitted more seats on that server than it had, usually seats from jobs that never released them.

## Endpoints for operating LicOps

The following table lists the endpoints for operating LicOps:

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/api/v1/status` | Whether LicOps is configured, its listen ports, and its sync with OpenLM Platform in `configurationManagement`. |
| `GET` | `/api/v1/stats` | Every pool and server with live seat counts. The source of the exact vendor and feature names. |
| `GET` | `/api/v1/rules/router-admission` | The admission rules LicOps is applying, including the built-in ones. Read-only: rules are defined in OpenLM Platform. |
| `GET` | `/api/v1/platform/servers` | The license servers LicOps received from OpenLM Platform. Answers `503` until it has them. |
| `GET`, `PUT` | `/api/v1/admission-controller/platform-credentials` | Read or store the OpenLM Platform connection. The secret reads back masked. |
| `POST` | `/api/v1/admission-controller/platform-credentials/check-connection` | Tests the credential in the request body, not the stored one. |
| `GET` | `/readyz` | Which listen ports are bound against which are wanted. Answers `503` until LicOps has its configuration. |
| `GET` | `/health/ready`, `/health/live`, `/health/start` | The standard OpenLM health checks. |

## Defaults

The following table lists the default settings:

| Setting | Default | What it means for you |
| --- | --- | --- |
| API port | `8100` | The port in every address on this page. |
| LicOps listen port | `30001` | For checkouts routed through LicOps. It speaks the FlexLM protocol, not HTTP. |
| Admit timeout | 15 minutes | A job not dispatched within this loses its seat. |
| Dispatch timeout | 2 hours | A started job keeps its seat this long without a release. |
| Cold-start `Retry-After` | 10 seconds | How long to wait after a cold-start `503`. |
| OpenLM Platform figures | About every 10 seconds | How often LicOps corrects its counts. After about 30 seconds without fresh figures, admit denies with `StaleSnapshot`. |

To change the timeouts, see [Set the session timeouts](https://www.openlm.com/documentation/cloud/deployment-operations/licops/deployment#set-the-session-timeouts).
