LMIO Watcher (HTTP REST API)¶
This document describes the REST API exposed by lmio-watcher for managing lookup definitions and lookup items (content stored in Elasticsearch, index pattern {lookup_id}.lkp).
All paths are prefixed with a tenant segment: /{tenant}/.... Example base URL: http://localhost:8080 (see your deployment / Docker port mapping).
Authentication and authorization¶
Endpoints use ASAB web auth. Typical resource scopes:
| Scope | Typical use |
|---|---|
lmio:lookup:access |
Read lookups and items (GET). |
lmio:lookup:edit |
Create/update/delete items, bulk CSV upload, lookup lifecycle edits that change data. |
lmio:lookup:global:edit |
Create or modify global lookups (not tenant-suffixed). |
Common conventions¶
- Success for simple operations:
{"result": "OK"}. - Failure (application-level):
{"result": "FAIL", "error": "<message>"}. - Tenant access:
{"result": "TENANT-NO-ACCESS"}with HTTP401if the client is not allowed to use the givenlookup_idfor that tenant. - Pagination (where supported):
p= page number (1-based),i= page size (default often10000). - Free-text filter (where supported): query parameter
f— case-insensitive substring match (see per-endpoint details).
Lookup ID¶
The lookup_id string identifies a lookup everywhere in URLs, Kafka messages, and Elasticsearch (index {lookup_id}.lkp).
When a lookup is created via POST /{tenant}/lookup-control, the effective id is derived from the JSON field name and whether the lookup is global:
| Lookup kind | How lookup_id is formed |
|---|---|
Tenant-specific (global is false or omitted) |
{name}.{tenant} — the lookup name (as provided in the request body), a dot, and the tenant taken from the URL path /{tenant}/.... Example: name: "countries" and path .../standard/... → lookup_id = countries.standard. |
Global (global: true) |
{name} only — the value of name is used as the full lookup_id (no .tenant suffix). Creating or editing global lookups requires the lmio:lookup:global:edit scope. |
For all HTTP routes, you pass this full lookup_id in the path (e.g. GET /{tenant}/lookup/countries.standard/...), not the bare name alone, when addressing a tenant lookup.
Lookup control (metadata)¶
Base path: /{tenant}/lookup-control
These endpoints manage lookup declarations (schema, labels, groups, etc.), not the bulk row data (use lookup content below for items).
| Method | Path | Description |
|---|---|---|
GET |
/{tenant}/lookup-control |
List lookup definitions available to the tenant. Query: p, i, f (filter matches id, label, name, group, description_title, description_content, default_expiration). Response: {"data": [...], "count": N}. |
GET |
/{tenant}/lookup-control/{lookup_id} |
Get one lookup definition and schema. |
POST |
/{tenant}/lookup-control |
Create a lookup. JSON body (validated against lookup create schema): name, label, group, type, global, keys, fields, descriptionTitle, descriptionContent, default_expiration, feedPath, etc. Tenant lookups use id {name}.{tenant} unless global is true. |
PUT |
/{tenant}/lookup-control/{lookup_id} |
Update lookup declaration. |
DELETE |
/{tenant}/lookup-control/{lookup_id} |
Delete the lookup. |
DELETE |
/{tenant}/lookup-control/{lookup_id}/items |
Clear all items in the lookup index (empty content, declaration remains). |
Scopes: GET requires lmio:lookup:access; mutating operations require lmio:lookup:edit (and lmio:lookup:global:edit for global lookups where applicable).
Lookup content (items)¶
Base path: /{tenant}/lookup/{lookup_id}
These endpoints operate on items (documents) inside the Elasticsearch lookup index.
| Method | Path | Description |
|---|---|---|
GET |
/{tenant}/lookup/{lookup_id} |
List items. Query: p, i, f (substring search across keys, item id, and declared text-like fields), s<field>=a asc / other values desc for sorting (_id maps to _item_id internally). Response: {"data": [...], "count": N}. |
GET |
/{tenant}/lookup/{lookup_id}/by |
List items where field = value (exact term). Required query: field, value. Same pagination/sort/filter p, i, s*, f as list. |
GET |
/{tenant}/lookup/{lookup_id}/{_id} |
Get one item by document id (URL-encoded; / in ids uses %2F). |
POST or PUT |
/{tenant}/lookup/{lookup_id} |
Create one item. JSON: keys / fields arrays (UI shape), optional _exp. |
PUT |
/{tenant}/lookup/{lookup_id}/{_id} |
Update one item. Same optional keys as create when changing key values (may re-index under a new id). |
DELETE |
/{tenant}/lookup/{lookup_id}/{_id} |
Delete one item. |
Scopes: read endpoints need lmio:lookup:access; write/delete need lmio:lookup:edit (and global edit for global lookups).
Bulk CSV upload (highlight)¶
The primary way to load or refresh many rows at once is CSV multipart upload. Internally, rows are applied via bulk update (update_by_bulk) against Elasticsearch.
Endpoints (equivalent)¶
| Method | Path |
|---|---|
POST |
/{tenant}/lookup/{lookup_id}/upload |
POST |
/{tenant}/lookup/{lookup_id}/import |
Both invoke the same handler. Pick whichever fits your tooling; /upload is the conventional name for UI “bulk import”.
Authorization¶
Requires lmio:lookup:edit (and global rules apply for global lookups).
Request¶
- Content-Type:
multipart/form-data - Body: one file part (first file in the multipart stream is used). The file is read from disk under
/tmp/<original_filename>(ensure filename is unique under concurrency in production). - Query parameters:
delimiter— CSV field delimiter. Default is;(semicolon). Usedelimiter=,for comma-separated files.
CSV shape¶
- First row must be a header (
csv.DictReader). - Columns:
_id— optional; document id if present._keys— optional; for compound keys, comma-separated parts in one cell (split on commas after strip)._exp— optional expiration (string cell, stripped).- Declared field names — any column whose name matches the lookup declaration
fieldsmap is imported; others are ignored.
Only fields present in the declaration are copied; geopoint columns accept either DMS “lat, lon” pairs or a numeric SPL geopoint value (same rules as single-item create).
Response¶
{
"result": "OK",
"count": 1234
}
count is the number of documents reported by the bulk update operation.
Errors¶
| Condition | Typical response |
|---|---|
| No file in multipart | {"result": "NO-FILE-UPLOADED"} with HTTP 400 |
| No access to lookup | {"result": "TENANT-NO-ACCESS"} with HTTP 401 |
Example (curl)¶
curl -X POST \
-H "Content-Type: multipart/form-data" \
-F "file=@countries.csv" \
"https://<host>:<port>/<tenant>/lookup/my_lookup.<tenant>/upload"
With comma-separated CSV:
curl -X POST \
-F "file=@data.csv" \
"https://<host>:<port>/<tenant>/lookup/my_lookup.<tenant>/upload?delimiter=,"
CSV export and sample¶
| Method | Path | Description |
|---|---|---|
GET |
/{tenant}/lookup/{lookup_id}/export |
Download all items as CSV (Content-Type: text/csv, attachment). Header includes _id, optionally _keys for compound-key lookups, then declared fields. Semicolon-separated; geopoint exported as DMS when applicable. |
GET |
/{tenant}/lookup/{lookup_id}/export_sample |
JSON response: {"result": "OK", "data": "<csv string>"} — first page of rows (up to 5 items) in the same CSV format for preview. |
Scope: lmio:lookup:access.
Notes for API clients¶
- Lookup id often ends with
.{tenant}for tenant-specific lookups; global lookups use ids without that suffix. - Item
_idin URLs must be percent-encoded when it contains special characters (e.g./→%2F). - List filter
fon items searches normalized text across keys,_item_id, and declared string-like / MAC-related fields; numeric-only fields may not match substring search. - Bulk upload does not replace the whole index by itself—it upserts each parsed row through the bulk path; use clear items (
DELETE .../lookup-control/{lookup_id}/items) first if you need an empty lookup before a full reload.
Related documentation¶
- Lookup types and declarations —
define.type, compound keys, declaration fields. - Lookup events — Kafka topic
lmio-lookupsfor declaration sync outside this HTTP API.