Overview
Reference for the REST endpoints used to manage an organization's private hotlists and their plates. It targets external integrations, gate-guard tooling, scheduled syncs, back-office scripts, that authenticate with a Corvid organization API key.
Prerequisite: An organization API key. See How to create API keys for your organization.
Authentication
Send the key on every request
Include the key in the Authorization header using the ApiKey scheme (not Bearer):
Authorization: ApiKey ak_XXXXXXXXXXXXXXXXXXXX Content-Type: application/json
Requests are attributed in the audit log to the identity API Key. Use descriptive names on each key so the trail stays readable.
Base URL
Production base URL:
https://api.lpr.corvidcloud.com
Common error codes
Status | Meaning |
401 | Missing / invalid / expired key, or the key is not tied to an organization. |
403 | Key resolved, but no matching org context. |
404 | Hotlist or plate not found for this organization. |
400 | Validation error — see the response body for field-level messages. |
409 | Duplicate (for example, a hotlist name already exists in the organization). |
Manage hotlists
Create a hotlist
POST /v2/hotlists
Request body:
{
"name": "Gate A watchlist",
"description": "Vehicles flagged by front-gate guards",
"isActive": true,
"isShared": false
}Field | Type | Required | Notes |
name | string | yes | 1–255 chars |
description | string | no | ≤1000 chars |
isActive | boolean | yes | false mutes matching |
isShared | boolean | no | Defaults to false |
Response 201:
{
"id": "hl_01H...",
"name": "Gate A watchlist",
"description": "Vehicles flagged by front-gate guards",
"isActive": true,
"isShared": false,
"createdAt": "2026-08-24T14:22:11Z",
"createdBy": "api_key"
}
List hotlists
GET /v2/hotlists
Query parameters (all optional):
Param | Type | Default | Notes |
limit | int | 100 | 1–100 |
nextToken | string | — | Opaque pagination cursor from the previous page |
shared | boolean | — | Filter to shared / non-shared hotlists |
search | string | — | Substring match on name |
sortBy | string | — | name, createdAt, updatedAt |
sortOrder | string | — | asc or desc |
Response 200:
{
"items": [
{
"id": "hl_01H...",
"name": "Gate A watchlist",
"description": "...",
"isActive": true,
"isShared": false,
"createdAt": "2026-08-24T14:22:11Z",
"createdBy": "api_key"
}
],
"nextToken": "eyJwayI6...",
"total": 12
}
Get a hotlist by ID
GET /v2/hotlists/{hotlistId}
Returns the same shape as Create a hotlist. Returns 404 if the id does not exist in the caller's organization.
Get a hotlist by name
GET /v2/hotlists/by-name/{hotlistName}
Case-sensitive exact match within the caller's organization. Returns 404 if not found.
Update a hotlist
PUT /v2/hotlists/{hotlistId}
All fields optional — send only what you want to change:
{
"name": "Gate A watchlist v2",
"description": "Updated by nightly sync",
"isActive": true,
"isShared": false
}
Delete a hotlist
DELETE /v2/hotlists/{hotlistId}
Deletes the hotlist and all its plates. Response 200:
{ "message": "Hotlist deleted successfully" }
Download a hotlist as CSV
GET /v2/hotlists/{hotlistId}/download
Returns a presigned S3 URL, valid for a short window. The CSV is cached in S3 and regenerated on the first request after changes.
{ "url": "https://s3.amazonaws.com/..." }
Manage plates inside a hotlist
List plates
GET /v2/hotlists/{hotlistId}/plates
Param | Type | Default | Notes |
limit | int | 100 | 1–100 |
nextToken | string | — | Pagination cursor |
search | string | — | Substring match on plateNumber |
Response 200:
{
"items": [
{
"plateNumber": "ABC1234",
"state": "TX",
"note": "Banned contractor truck",
"createdAt": "2026-08-20T09:01:00Z",
"createdBy": "api_key",
"expiredAt": 1735689600
}
],
"nextToken": null,
"total": 47
}
Upsert a single plate
PUT /v2/hotlists/{hotlistId}/plates/{plateNumber}
Request body — all fields optional; newPlateNumber renames the plate:
{
"newPlateNumber": "ABC1234",
"state": "TX",
"note": "Renewed 2026-Q4",
"expiredAt": 1767225600
}Field | Type | Notes |
newPlateNumber | string | 1–20 chars; rename the plate |
state | string | 2-letter code; sending the key at all clears or updates it |
note | string | ≤1000 chars |
expiredAt | int | Unix seconds; after this, the plate stops matching |
Bulk add or upsert plates
POST /v2/hotlists/{hotlistId}/plates/bulk
Use this for the "quickly add" use case — the caller can push a batch instead of one call per plate.
Request body:
{
"plates": [
{ "plateNumber": "ABC1234", "state": "TX", "note": "Watch — Gate A" },
{ "plateNumber": "XYZ7890", "state": "CA", "note": "Watch — Gate B", "expiredAt": "2026-12-31" }
]
}expiredAt on each item accepts a YYYY-MM-DD string (converted to a future Unix timestamp; past dates are rejected) or an integer Unix timestamp.
Response 200:
{
"successful": 2,
"failed": 0,
"errors": [],
"createdPlates": [
{ "plateNumber": "ABC1234", "state": "TX", "note": "Watch — Gate A", "createdAt": "...", "createdBy": "api_key" }
],
"createdCount": 1,
"updatedCount": 1
}createdCount = newly inserted; updatedCount = plates that already existed and were overwritten. failed and errors[] capture per-row validation issues without aborting the batch.
Bulk delete plates
DELETE /v2/hotlists/{hotlistId}/plates/bulk
Max 100 items per call.
{
"plates": [
{ "plateNumber": "ABC1234", "state": "TX" },
{ "plateNumber": "XYZ7890", "state": "CA" }
]
}Response 200:
{
"successful": 2,
"failed": 0,
"errors": [],
"deletedPlates": [
{ "plateNumber": "ABC1234", "state": "TX" }
]
}
Search for a plate
Global plate search
GET /v2/hotlists/global/plates/{plateNumber}
Searches for a plate across the caller org's private hotlists and every public hotlist the org is subscribed to. This is the endpoint gate guards should call to answer "is this plate flagged?".
Param | Type | Default | Notes |
state | string | — | 2-letter code; strongly recommended |
orgId | string | — | Only needed for cross-org superuser lookups |
limit | int | — | 1–100; omit for full merged list |
nextToken | string | — | Opaque cursor for paginated mode |
Response 200:
{
"items": [
{
"hotlistId": "hl_01H...",
"hotlistName": "Gate A watchlist",
"plateNumber": "ABC1234",
"state": "TX",
"reason": "Banned contractor truck",
"hotlistType": "private",
"expiredAt": 1767225600
}
],
"lastKey": null
}
End-to-end flow
A typical onboarding sequence for a gate-guard integration:
POST
/v2/hotlists— create a new private hotlist and capture the returned id.POST
/v2/hotlists/{hotlistId}/plates/bulk— load the initial batch of plates.GET
/v2/hotlists/global/plates/{plateNumber}?state=XX— for each observed plate, check whether it is flagged.Keep the hotlist current with PUT / DELETE plate endpoints (single or bulk), and periodically fetch GET
/v2/hotlists/{hotlistId}/platesto reconcile against the source of truth.
Operational notes
Rate limits — API Gateway usage plans apply per stage. Ask the Corvid team for the current quota if you plan to sync more than a few thousand plates per minute.
Audit trail — mutations show up under the identity API Key. Issue one key per external integrator if you need to distinguish callers.
Key scope — an organization API key currently also reaches public-hotlist and subscription endpoints. If your integration only needs private management, restrict its use to the routes in this document.
Key rotation — rotate keys through Manage Organization → API Keys. Revoked keys stop authenticating immediately; there is no server-side cache.
Related articles
How to create API keys for your organization
What environments does we support?
What is the Permissions System?
