Call the admission API
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:
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:
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.
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.
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}'
{ "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 untilexpiresAt, 15 minutes by default, unless you dispatch first.Deny: nothing was counted.reasonsays why. See Deny reasons. Do not start the job. You can ask again later.
You never name a server. LicOps chooses it, applying your 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:
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"}'
{
"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:
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:
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
200from admit as "a decision arrived", then readdecision. - Treat
503from admit as a pause, not a refusal. - Log
NoServerAvailableandNoCapacityseparately: 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
503ThrottleDeferredfrom dispatch, dispatch again with the same ID. On503NoCapacityAtDispatch, 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.
The documentation team might draft this page using a large language model. A technical writer reviews every page and verifies it against the product before publishing it. A human makes the final decision.