---
title: Call the admission API
description: Gate jobs in OpenLM Platform from your own scheduler, wrapper, or service by calling the OpenLM LicOps admit, dispatch, and release endpoints.
product: OpenLM Platform
---

If something other than Slurm, LSF, or Kubernetes starts your jobs, such as another scheduler, a portal, a CI system, or a launcher script, call the LicOps admission API yourself. Any tool that can send an HTTP `POST` with a JSON body can do it, and you install nothing on the LicOps side.

## Start from the integration kit

The integration kit from OpenLM has two ready clients that already handle the ordering and every failure path.

### Wrap any command

To wrap any command, use `bash/openlm-wrap.sh`. It holds one seat for as long as the command runs, and releases it however the command ends:

```bash
export OL_ROUTER_URL=http://<licops-host>:8100
./bash/openlm-wrap.sh MLM MATLAB -- matlab -batch mysim
```

It needs `curl` and `jq`. `OL_COUNT` sets the number of seats. `OL_FAIL_MODE` decides what happens when LicOps is unreachable, and defaults to `closed`: the command does not run.

### Call from Python

From Python, use `python/openlm_licops.py`. It needs only the Python 3.7 standard library:

```python
import os
from openlm_licops import LicOpsClient

ol = LicOpsClient("http://<licops-host>:8100")

with ol.seat(vendor="MLM", feature="MATLAB", count=1) as seat:
    target = seat.dispatch(node_ip="10.42.7.31", job_id="918273")
    os.environ["LM_LICENSE_FILE"] = target.license_file
    run_the_application()
# The seat is released on the way out, including when the block raises.
```

To build a client in another language, the kit has an OpenAPI 3.0 description of the three endpoints, `openapi/openlm-licops-admission.yaml`. Import it into an API tool, or generate a client from it.

## The three requests

All three are `POST` requests with a JSON body, under `http://<licops-host>:8100/api/v1/admission-controller`. For every field and response, see [API reference](https://www.openlm.com/documentation/cloud/deployment-operations/licops/reference).

### Admit, before the job is queued

Generate a session ID first, a UUID, and keep it until the job ends. You pass the same ID to all three requests, and it is what makes a retry safe: an admit repeated with the same ID returns the original answer instead of counting a second seat, as long as the session is still valid. A denied admit is not stored, so repeating it asks again. Reuse an ID only with the same vendor, feature, and seat count: anything else is refused with `400`. Use a new ID for every new job.

```bash
curl -s -X POST http://<licops-host>:8100/api/v1/admission-controller/admit \
  -H 'Content-Type: application/json' \
  -d '{"sessionId":"8e29c1b2-6d3a-4f0e-9d51-7c2a4b6e8f10","vendor":"MLM","feature":"MATLAB","user":"jdoe","host":"login01","totalQuantity":1}'
```

```json
{ "decision": "Admit", "sessionId": "8e29c1b2-6d3a-4f0e-9d51-7c2a4b6e8f10", "expiresAt": "2026-10-15T11:42:19Z", "reason": null }
```

A `200` means LicOps reached a decision, not that it admitted the job. Read `decision`:

- **`Admit`**: the seat is counted for this session until `expiresAt`, 15 minutes by default, unless you dispatch first.
- **`Deny`**: nothing was counted. `reason` says why. See [Deny reasons](https://www.openlm.com/documentation/cloud/deployment-operations/licops/reference#deny-reasons). Do not start the job. You can ask again later.

You never name a server. LicOps chooses it, applying your [admission rules](https://www.openlm.com/documentation/cloud/deployment-operations/licops/admission-rules).

### Dispatch, when the job starts

Call dispatch on the node that runs the job, as late as you can, and use the server it returns:

```bash
curl -s -X POST http://<licops-host>:8100/api/v1/admission-controller/dispatch \
  -H 'Content-Type: application/json' \
  -d '{"sessionId":"8e29c1b2-6d3a-4f0e-9d51-7c2a4b6e8f10","executionNodeIp":"10.42.7.31","jobId":"918273"}'
```

```json
{
  "sessionId": "8e29c1b2-6d3a-4f0e-9d51-7c2a4b6e8f10",
  "hostName": "lic01.example.com",
  "port": "27000",
  "licenseFile": "27000@lic01.example.com",
  "fallbackTargets": [
    { "hostName": "lic02.example.com", "port": "27000", "licenseFile": "27000@lic02.example.com" }
  ]
}
```

Set `LM_LICENSE_FILE`, or the application's own variable such as `MLM_LICENSE_FILE`, to `licenseFile`, exactly as returned. For a three-server redundant license server, it already lists all three machines. To give the application other servers to fall back on, join `fallbackTargets` behind it with `:` on Linux or `;` on Windows:

```bash
export LM_LICENSE_FILE=27000@lic01.example.com:27000@lic02.example.com
```

The server can differ from the one LicOps planned at admit time: if that server failed in between, dispatch moves the seat to a healthy one. Always use what dispatch returns. Dispatch also extends the session to its running timeout, 2 hours by default.

### Release, when the job ends

To release the seat, run this command:

```bash
curl -s -X POST http://<licops-host>:8100/api/v1/admission-controller/release \
  -H 'Content-Type: application/json' \
  -d '{"sessionId":"8e29c1b2-6d3a-4f0e-9d51-7c2a4b6e8f10"}'
```

Release always answers `200`, so it is safe to call from a `trap` or `finally` block that might run twice, or for a job that was never admitted. Set it up before you admit, so no path out of the job skips it. If LicOps is unreachable at release, log it and continue: never fail a job because its release failed. LicOps reclaims the seat when the session times out.

## Handle the answers that are not errors

The following table lists the answers that are not errors, what they mean, and what to do:

| Answer | What it means | What to do |
| --- | --- | --- |
| Admit `503` with `Retry-After` | LicOps started moments ago and has not caught up with OpenLM Platform. | Wait for the `Retry-After` seconds, 10 by default, and admit again with the same session ID. Do not fail the job. |
| Admit `400` | The body is invalid: an empty `sessionId`, `vendor`, or `feature`, or a seat count below 1, or a session ID reused with a different vendor, feature, or seat count. | Fix the request. An empty shell variable is the usual cause. |
| Dispatch `404` | No such session: the ID is wrong, the session already ended, or the admit timeout ran out before the job started. | Admit again with a new session ID. Do not retry the dispatch. |
| Dispatch `503`, `ThrottleDeferred` | A Throttle rule is pacing checkouts. | Wait for `Retry-After` and dispatch again with the same session ID. The seat is still yours, so do not admit again. |
| Dispatch `503`, `NoCapacityAtDispatch` | The server the seat was on failed, and no other server in the pool has a free seat. | LicOps has released the session and sends no `Retry-After`. Admit again with a new session ID. |

## Checklist for your own client

Confirm that your client does the following:

- Generate the session ID yourself and keep it until the job ends.
- Treat `200` from admit as "a decision arrived", then read `decision`.
- Treat `503` from admit as a pause, not a refusal.
- Log `NoServerAvailable` and `NoCapacity` separately: the first usually means a misspelled vendor or feature, the second a real shortage.
- Dispatch after the job is placed, and use the server it returns.
- On `503` `ThrottleDeferred` from dispatch, dispatch again with the same ID. On `503` `NoCapacityAtDispatch`, admit again with a new ID.
- Release from code that runs on every ending and set it up before you admit.
- Never fail a job because its release failed.
- Use a short HTTP timeout, from 2 to 10 seconds, so a slow LicOps cannot stall your submissions.
- Decide separately what admit and dispatch do when LicOps is unreachable, and write the decision down.
