OmniLCS REST API Reference
The OmniLCS REST API is served over HTTPS on port 8443. The base URL is https://<host>:8443/api.
API documentation (Swagger UI) is available at https://<host>:8443/api/docs. The OpenAPI schema is at https://<host>:8443/api/schema.
Authentication
The API does not currently enforce authentication. Access should be restricted at the network level.
Response Format
All responses use JSON. Successful responses include a "status": "ok" field. Error responses include "status": "error" and a "reason" field.
System Status
GET /api/status
Returns system health and operational status.
Response (200)
{
"status": "ok",
"version": "1.0.0",
"name": "OmniLCS",
"diameter_peers": [
{
"host": "dra01.epc.mnc380.mcc313.3gppnetwork.org",
"realm": "epc.mnc380.mcc313.3gppnetwork.org",
"state": "Connected",
"transport": "sctp"
}
],
"active_sessions": 2,
"completed_sessions": 47,
"cells_loaded": 128,
"cell_sync": {
"last_sync": "2025-01-15T10:30:00Z",
"last_result": "ok (128 cells)",
"sync_count": 42
},
"uptime_seconds": 86400
}
| Field | Type | Description |
|---|---|---|
version | string | Application version |
name | string | Instance name |
diameter_peers | array | Connected Diameter peers with state |
active_sessions | integer | Number of in-progress location sessions |
completed_sessions | integer | Number of completed location sessions |
cells_loaded | integer | Number of cells in the database |
cell_sync | object | InfluxDB sync status |
uptime_seconds | integer | Process uptime in seconds |
Location Services
POST /api/location
A single, harmonised "get location" call. The caller supplies the IMSI (and optionally a desired method/accuracy); OmniLCS decides which 3GPP LCS role and interface to use to satisfy it, and reports which one answered in the source field.
Every option is available here
OmniLCS is co-located as a GMLC and an E-SMLC (the LTE LCS network functions of TS 23.271) and also speaks MAP/Lg for legacy 2G/3G — so every standard way to locate a subscriber is reachable through this one endpoint:
| Option | 3GPP role | Interface(s) | How it locates | Best for |
|---|---|---|---|---|
| A — query the network (4G) | GMLC | SLh (TS 29.173) + SLg (TS 29.172) | No active positioning — the serving MME returns the UE's serving cell (ECGI) from its context | Fast, always-available coarse location; idle UEs (last-known) |
| B — actively position (4G) | E-SMLC | SLs / LCS-AP (TS 29.171), tunnelling LPP (TS 36.355) to the UE and LPPa (TS 36.455) to the eNB | OmniLCS runs E-CID / GNSS / OTDOA against the UE and radio | Precise fixes when the UE/radio support it |
| C — query the network (2G/3G) | GMLC | MAP / Lg over SS7 (TS 29.002): SRI-for-LCS to the HLR, then ProvideSubscriberLocation to the serving MSC/SGSN | The serving MSC/SGSN returns the serving cell (Cell-ID / TA class) | Subscribers camped on GERAN/UTRAN |
Selection: for 4G, the requested method/accuracy chooses A (cell/last-known) or B (ecid/gnss/otdoa/hybrid). Setting rat to 2g/3g chooses C regardless of method. In A/B the serving MME is always in the path (SLg responder or SLs relay); in C the serving MSC/SGSN is. See 2G/3G location over MAP/Lg.
See Positioning Methods for how each method (Cell ID, E-CID, OTDOA, A-GNSS, hybrid) is obtained, their accuracy, and the accuracy→method auto-selection.
Request Body
{
"imsi": "001010000000001",
"method": "cell",
"location_type": 1,
"timeout": 30000,
"mme_host": "mme01.epc.mnc380.mcc313.3gppnetwork.org"
}
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
imsi | string | Yes | -- | IMSI of the UE to locate |
method | string | No | cell | Positioning method: cell, ecid, gnss, otdoa, hybrid. Active methods (ecid/gnss/otdoa/hybrid) drive the E-SMLC over SLs; cell uses an SLg PLR. |
accuracy | integer | No | -- | Requested horizontal accuracy in metres. If set, auto-selects the method (tighter → GNSS/OTDOA/E-CID); for the cell/last-known path it is also carried to the MME as LCS-QoS. |
location_type | integer | No | 1 | SLg-Location-Type (cell/last-known path): 0 current, 1 current or last known, 2 initial. The default 1 returns the last-known serving cell for idle UEs rather than failing a current-location request. |
mme_host | string | No | -- | Destination-Host (serving MME). If omitted, it is resolved via an SLh LCS-Routing-Info-Request to the HSS. |
rat | string | No | 4g | Radio access technology. 2g/3g route over MAP/Lg (Option C) to the serving MSC/SGSN; 4g uses SLg/SLs (Options A/B) as selected by method. |
node_gt | string | No | -- | 2G/3G only: serving MSC/SGSN global title. If omitted, it is resolved via a MAP SendRoutingInfoForLCS to the HLR. |
timeout | integer | No | 30000 | Timeout in milliseconds |
The requested
method(oraccuracy) selects how OmniLCS positions the UE: active methods run the OmniLCS E-SMLC (LPP/LPPa over SLs), whilecell/last-known queries the serving MME over SLg. On the SLg path the MME ultimately decides what it can return (typically the serving cell ECGI).
Response (200)
{
"status": "ok",
"imsi": "001010000000001",
"method": "cell",
"source": "slg",
"latitude": 40.7128,
"longitude": -74.0060,
"altitude": null,
"uncertainty": 10.5,
"confidence": null,
"cell_id": 43559,
"plmn": {"mcc": "001", "mnc": "01"},
"ecgi": "00F1100000AA27",
"serving_node": {"name": "mme01...", "realm": "epc..."},
"duration_ms": 5230,
"timestamp": "2025-01-15T10:30:00Z"
}
The response merges every field the positioner returned with the request envelope, so the exact set varies by path. latitude/longitude are decoded from the GAD Location-Estimate; source is "slg" for results from the serving MME over SLg, or the positioning method (gnss, ecid, …) for active E-SMLC fixes. Cell/SLg fixes additionally include the decoded ECGI (cell_id + plmn) alongside the raw ecgi. Binary AVPs (raw ECGI, GAD estimate, Serving-Node) are hex-encoded.
| Field | Type | Description |
|---|---|---|
imsi | string | UE identifier |
method | string | Method requested |
source | string | Actual positioning source used (slg, gnss, ecid, …) |
latitude | float/null | Latitude in decimal degrees |
longitude | float/null | Longitude in decimal degrees |
altitude | float/null | Altitude in meters (if available) |
uncertainty | float/null | Horizontal position uncertainty in meters |
uncertainty_altitude | float/null | Vertical uncertainty in meters (when present) |
confidence | integer/null | Confidence percentage |
shape | string/null | GAD shape of the estimate (e.g. ellipsoid_point_with_uncertainty_circle) |
cell_id | integer/null | Decoded E-UTRAN cell-id (cell/SLg path) |
plmn | object/null | Decoded serving PLMN {mcc, mnc} (cell/SLg path) |
ecgi | string/null | Raw 7-byte ECGI, hex (cell/SLg path) |
serving_node | object/null | Serving MME identity (Name/Realm) |
age_of_location_estimate | integer/null | Age of the estimate in minutes (when present) |
accuracy_fulfilment | string/null | Whether the requested accuracy was met |
ecid_measurements | object/null | E-CID radio measurements (ecid method) |
duration_ms | integer | Time taken in milliseconds |
timestamp | string | ISO 8601 timestamp |
Error Responses
| Status | Reason | Description |
|---|---|---|
| 400 | "imsi is required" | Missing IMSI parameter |
| 404 | "User not found" | IMSI unknown |
| 404 | "User not connected" | UE not attached to network |
| 422 | "No MME host available for this subscriber" | No MME to route the request |
| 504 | "Positioning timed out" | Positioning method did not complete in time |
| 500 | (varies) | Internal error |
Examples
Option A — coarse location via the GMLC role (SLh + SLg). Asking for cell (or any last-known request) resolves the serving MME and returns its serving cell:
curl -sk -X POST https://localhost:8443/api/location \
-H "Content-Type: application/json" \
-d '{"imsi": "001010000000001", "method": "cell"}'
{
"status": "ok",
"imsi": "001010000000001",
"method": "cell",
"source": "slg",
"latitude": -37.8183,
"longitude": 144.9671,
"altitude": null,
"uncertainty": 1273.5,
"confidence": null,
"cell_id": 43559,
"plmn": {"mcc": "001", "mnc": "01"},
"ecgi": "00F1100000AA27",
"serving_node": {"name": "mme01.epc.mnc001.mcc001.3gppnetwork.org", "realm": "epc.mnc001.mcc001.3gppnetwork.org"},
"duration_ms": 412,
"timestamp": "2026-06-22T01:14:55.218000Z"
}
Option B — precise location via the E-SMLC role (SLs / LPP). Asking for gnss (or supplying a tight accuracy) drives an active fix; source reflects the method that answered:
curl -sk -X POST https://localhost:8443/api/location \
-H "Content-Type: application/json" \
-d '{"imsi": "001010000000001", "method": "gnss", "timeout": 30000}'
{
"status": "ok",
"imsi": "001010000000001",
"method": "gnss",
"source": "gnss",
"latitude": -37.8200,
"longitude": 144.9834,
"altitude": 558,
"uncertainty": 9.5,
"uncertainty_altitude": 1.1,
"confidence": 90,
"shape": "ellipsoid_point_with_altitude_and_uncertainty_ellipsoid",
"duration_ms": 10077,
"timestamp": "2026-06-22T01:18:02.640000Z"
}
Equivalently, -d '{"imsi": "001010000000001", "accuracy": 20}' auto-selects A-GNSS (≤ 50 m → A-GNSS) and returns the same shape of response.
GET /api/location
List recent completed location fixes.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 50 | Maximum number of results |
Response (200)
{
"status": "ok",
"data": [
{
"imsi": "001010000000001",
"method": "gnss",
"state": "completed",
"latitude": 40.7128,
"longitude": -74.0060,
"uncertainty": 10.5,
"source": "gnss",
"created_at": "2025-01-15T10:29:55Z",
"completed_at": "2025-01-15T10:30:00Z"
}
],
"count": 1
}
GET /api/location/:imsi
Get the last known location for a UE by IMSI.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
imsi | string | IMSI of the UE |
Response (200)
{
"status": "ok",
"imsi": "001010000000001",
"latitude": 40.7128,
"longitude": -74.0060,
"altitude": null,
"uncertainty": 10.5,
"confidence": null,
"source": "gnss",
"timestamp": "2025-01-15T10:30:00Z"
}
Error Responses
| Status | Reason |
|---|---|
| 404 | "No location found for IMSI" |
| 404 | "No completed location for IMSI" |
GET /api/location/:imsi/history
Get location fix history for an IMSI.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
imsi | string | IMSI of the UE |
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
from | string | -- | Start of time range (ISO 8601 or date) |
to | string | -- | End of time range (ISO 8601 or date) |
limit | integer | 100 | Maximum number of results |
Response (200)
{
"status": "ok",
"data": [
{
"timestamp": "2025-01-15T10:30:00Z",
"imsi": "001010000000001",
"method": "gnss",
"latitude": 40.7128,
"longitude": -74.0060,
"altitude": null,
"uncertainty": 10.5,
"confidence": null,
"source": "gnss",
"duration_ms": 5230
}
],
"count": 1
}
GET /api/location/:imsi/history/csv
Export location history for an IMSI as a CSV file.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
imsi | string | IMSI of the UE |
Query Parameters
Same as /api/location/:imsi/history.
Response (200)
Returns a CSV file download with headers:
timestamp,imsi,method,latitude,longitude,altitude,uncertainty,confidence,source,duration_ms
Content-Type: text/csv
Content-Disposition: attachment; filename="location_history_<imsi>_<date>.csv"
Cell Database
GET /api/cells
List all cells in the database.
Response (200)
{
"status": "ok",
"data": [
{
"cell_id": "001-01-0001-01",
"latitude": 40.7128,
"longitude": -74.0060,
"pci": 100,
"earfcn": 1300,
"radius": 500,
"azimuth": null,
"height": null,
"prs_config": null,
"updated_at": "2025-01-15T10:00:00Z"
}
],
"count": 1
}
GET /api/cells/:id
Get a single cell by cell_id.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Cell identifier |
Response (200)
{
"status": "ok",
"data": {
"cell_id": "001-01-0001-01",
"latitude": 40.7128,
"longitude": -74.0060,
"pci": 100,
"earfcn": 1300,
"radius": 500,
"azimuth": null,
"height": null,
"prs_config": {
"bandwidth": 50,
"config_index": 0,
"num_dl_frames": 1,
"cp_length": null,
"num_antenna_ports": null
},
"updated_at": "2025-01-15T10:00:00Z"
}
}
Error Response
| Status | Reason |
|---|---|
| 404 | "Cell not found: <id>" |
POST /api/cells
Create a new cell.
Request Body
{
"cell_id": "001-01-0001-01",
"latitude": 40.7128,
"longitude": -74.0060,
"pci": 100,
"earfcn": 1300,
"radius": 500,
"azimuth": 120.0,
"height": 30.0,
"prs_config": {
"bandwidth": 50,
"config_index": 0,
"num_dl_frames": 1,
"cp_length": "normal",
"num_antenna_ports": 2
}
}
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
cell_id | string | Yes | -- | Unique cell identifier |
latitude | float | Yes | -- | Cell latitude (-90 to 90) |
longitude | float | Yes | -- | Cell longitude (-180 to 180) |
pci | integer | No | -- | Physical Cell Identity (0-503) |
earfcn | integer | No | -- | E-UTRA Absolute Radio Frequency Channel Number |
radius | integer | No | 1000 | Coverage radius in meters |
azimuth | float | No | -- | Antenna azimuth in degrees |
height | float | No | -- | Antenna height in meters |
prs_config | object | No | -- | PRS configuration for OTDOA |
tac | integer | No | -- | Tracking Area Code (used for 4G CAP alert broadcast targeting) |
lac | integer | No | -- | Location Area Code (used for 2G and 3G CAP alert broadcast targeting) |
rat | string | No | -- | Radio Access Technology: "4g", "3g", or "2g" |
Response (201)
Returns the created cell in the same format as GET /api/cells/:id.
Error Responses
| Status | Reason |
|---|---|
| 400 | "cell_id is required" |
| 400 | "latitude and longitude are required" |
PUT /api/cells/:id
Update an existing cell. Only provided fields are updated.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Cell identifier |
Request Body
Any cell fields to update (same as POST, but all fields are optional).
Response (200)
Returns the updated cell.
Error Response
| Status | Reason |
|---|---|
| 404 | "Cell not found: <id>" |
DELETE /api/cells/:id
Delete a cell from the database.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Cell identifier |
Response (204)
Empty body on success.
Error Response
| Status | Reason |
|---|---|
| 404 | "Cell not found: <id>" |
GET /api/cells/nearby
Find cells near a geographic point.
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
lat | float | Yes | -- | Latitude of search center |
lon | float | Yes | -- | Longitude of search center |
radius | float | No | 10 | Search radius in kilometers |
Response (200)
{
"status": "ok",
"data": [
{
"cell_id": "001-01-0001-01",
"latitude": 40.7128,
"longitude": -74.0060,
"pci": 100,
"earfcn": 1300,
"distance_km": 0.523
}
],
"count": 1
}
Results are sorted by distance (nearest first). Each entry includes a distance_km field.
Error Response
| Status | Reason |
|---|---|
| 400 | "lat and lon query parameters are required" |
POST /api/cells/sync
Trigger an immediate InfluxDB cell sync.
Request Body
None required.
Response (200)
{
"status": "ok",
"cells_synced": 128
}
Error Responses
| Status | Reason |
|---|---|
| 500 | "Sync failed: <reason>" |
| 503 | "Cell sync service unavailable" |
Deferred Location (GMLC)
Manage periodic and geo-fence triggered location sessions. See the GMLC & Le Interface guide for full details on session types and Diameter integration.
GET /api/deferred_location
List all active deferred location sessions.
Response (200):
{
"status": "ok",
"count": 1,
"data": [
{
"session_id": "a1b2c3d4-e5f6-...",
"type": "periodic",
"imsi": "001010000000001",
"method": "cell",
"client_name": "rest-api",
"status": "active",
"interval_ms": 60000,
"remaining_reports": 7,
"total_reports": 10,
"started_at": "2026-04-09T10:00:00Z",
"last_fix_at": "2026-04-09T10:03:00Z"
}
]
}
POST /api/deferred_location
Create a new deferred location session.
Periodic session request body:
{
"type": "periodic",
"imsi": "001010000000001",
"method": "cell",
"interval_seconds": 60,
"count": 10
}
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | "periodic" |
imsi | string | Yes | Subscriber IMSI |
method | string | No | Positioning method: cell, ecid, gnss, otdoa. Default: cell |
interval_seconds | integer | Yes | Seconds between fixes |
count | integer | Yes | Total number of fixes to perform |
Triggered session request body:
{
"type": "triggered",
"imsi": "001010000000001",
"method": "cell",
"event_type": "entering",
"poll_interval_seconds": 30,
"max_reports": 0,
"areas": [
{
"type": "circle",
"center": {"lat": -33.8688, "lon": 151.2093},
"radius_meters": 500
}
]
}
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | "triggered" |
imsi | string | Yes | Subscriber IMSI |
method | string | No | Positioning method. Default: cell |
event_type | string | Yes | "entering", "leaving", or "being_inside" |
poll_interval_seconds | integer | No | Seconds between position polls. Default: 30 |
max_reports | integer | No | Max trigger reports. 0 = unlimited |
areas | array | Yes | List of area definitions (circle or polygon) |
Response (201):
{"status": "ok", "message": "Periodic session created"}
GET /api/deferred_location/:session_id
Get the status of a deferred session.
Response (200):
{
"status": "ok",
"data": {
"session_id": "a1b2c3d4-...",
"type": "periodic",
"imsi": "001010000000001",
"status": "active",
"remaining_reports": 7,
"total_reports": 10
}
}
DELETE /api/deferred_location/:session_id
Cancel an active deferred session.
Response (200):
{"status": "ok", "message": "Session cancelled"}
| Status | Error |
|---|---|
| 400 | Missing or invalid parameters |
| 404 | Session not found |
CAP Alerts
POST /api/cap
Submit a CAP XML alert for processing. The alert is parsed, polygon warning areas are resolved to TACs/LACs via the cell database, and the alert is either queued for approval or auto-broadcast depending on configuration.
Request Body
{
"xml": "<alert xmlns=\"urn:oasis:names:tc:emergency:cap:1.2\">...</alert>"
}
| Parameter | Type | Required | Description |
|---|---|---|---|
xml | string | Yes | Complete CAP v1.2 XML alert document |
Response (201)
{
"status": "ok",
"data": {
"id": "a1b2c3d4-e5f6-...",
"status": "pending",
"source": "http_post",
"received_at": "2025-01-15T10:30:00Z",
"matched_cells": 42,
"tacs": [100, 101],
"lacs": [5001],
"mcc": "001",
"mnc": "01",
"broadcast_params": {
"message_id": 4370,
"repetition_period": 30,
"num_broadcasts": 10,
"message_text": "Tornado Warning...",
"event": "Tornado Warning",
"severity": "Extreme",
"urgency": "Immediate"
}
}
}
The status field is "pending" when require_approval is true, or "sent" when auto-approved.
Error Responses
| Status | Reason |
|---|---|
| 400 | "xml field is required" |
| 422 | Parse error details |
GET /api/cap
List all alerts across all states (pending, active, history).
Response (200)
{
"status": "ok",
"data": {
"pending": [...],
"active": [...],
"history": [...]
}
}
GET /api/cap/:id
Get a single alert by ID.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Alert UUID |
Response (200)
Returns the alert object.
Error Response
| Status | Reason |
|---|---|
| 404 | "Alert not found: <id>" |
PUT /api/cap/:id
Approve or reject a pending alert.
Request Body
{
"action": "approve",
"operator": "operator1"
}
| Parameter | Type | Required | Description |
|---|---|---|---|
action | string | Yes | "approve" or "reject" |
operator | string | No | Operator name for audit trail (defaults to "unknown") |
Response (200)
Returns the updated alert object.
Error Responses
| Status | Reason |
|---|---|
| 400 | "action must be 'approve' or 'reject'" |
| 404 | "Alert not found: <id>" |
Error Response Format
All error responses follow this structure:
{
"status": "error",
"reason": "Human-readable error description"
}
Common HTTP status codes:
| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created (cell creation) |
| 204 | No Content (cell deletion) |
| 400 | Bad Request (missing or invalid parameters) |
| 404 | Not Found (unknown resource) |
| 422 | Unprocessable Entity (valid request but cannot be fulfilled) |
| 500 | Internal Server Error |
| 503 | Service Unavailable |
| 504 | Gateway Timeout (positioning timeout) |