Flock Safety API vs Motorola Vigilant API for ALPR Data Integration 2025
Quick Summary: Flock Safety vs Motorola Vigilant for ALPR Integration
For most modern integrations — smart city dashboards, HOA platforms, university security — Flock Safety's REST API is the better choice: it offers webhooks, OAuth 2.0, clean JSON schemas, and active developer documentation. For law enforcement agencies with existing Motorola infrastructure, Criminal Justice Information Services (CJIS) mandates, and a need to query the LEARN database's national breadth, Motorola Vigilant remains the authoritative option despite its steeper integration complexity and legacy SOAP endpoints.
The stakes are higher than ever in 2025. The FBI is actively pursuing a nationwide procurement contract for ALPR access — and industry reporting (404 Media, May 2026) confirms that Flock Safety and Motorola Vigilant are the two vendors realistically capable of fulfilling that contract. If you're building integrations on either platform today, federal procurement activity could affect API terms, rate limits, and pricing within your production timeline.
Side-by-Side Feature Comparison Table
| Dimension | Flock Safety API | Motorola Vigilant API | |---|---|---| | Auth Method | OAuth 2.0 (client credentials) | HMAC-signed token, CJIS-compliant session tokens | | Primary Data Format | REST + JSON | REST (modern) + SOAP (legacy LEARN endpoints) | | Webhook / Push Support | ✓ Native webhooks | ✗ Polling only | | Bulk Query Rate Limit | ~1,000 reads/request, paginated | Varies by agency contract; typically lower burst | | Data Retention Policy | Configurable per deployment (30–365 days) | Agency-controlled; LEARN retains indefinitely | | CJIS Compliance | ✓ (growing coverage) | ✓ (established, full audit trail) | | SOC 2 Type II | ✓ | ✓ | | Pricing Model | SaaS subscription + API tier | Enterprise contract; per-query or site license | | SDK / Official Client | No official SDK; OpenAPI spec available | No official SDK; SOAP WSDLs + REST Postman collection | | Sandbox / Test Env | ✓ Available | ✗ Limited / not publicly available |
Who Should Read This Guide
This guide targets backend engineers and integration architects who need to connect ALPR data streams into operational systems: fleet management tools, RMS/CAD platforms, smart city dashboards, or security operations centers. You're expected to be comfortable with Python, Node.js, or TypeScript and familiar with REST API concepts.
Key Decision Factors at a Glance
- Camera network coverage: Flock data only exists where Flock cameras are deployed. Motorola Vigilant aggregates from many sources via LEARN.
- Real-time vs batch: Flock wins on real-time with native webhooks. Vigilant requires polling.
- Compliance posture: Both are SOC 2 Type II. Vigilant has the longer CJIS track record.
- Developer experience: Flock has an OpenAPI spec and sandbox. Vigilant has better enterprise support SLAs.
Authentication and API Security Architecture
Flock Safety: OAuth 2.0 and API Key Management
Flock Safety uses the OAuth 2.0 client credentials flow. You exchange a client_id and client_secret for a short-lived Bearer token (typically 1-hour TTL) via a /oauth/token endpoint. All subsequent API calls include Authorization: Bearer <token>. Flock also supports API key authentication for simpler integrations, but client credentials is preferred for server-side apps with token rotation.
import os
import time
import requests
from dataclasses import dataclass, field
@dataclass
class FlockAuthClient:
client_id: str = field(default_factory=lambda: os.environ["FLOCK_CLIENT_ID"])
client_secret: str = field(default_factory=lambda: os.environ["FLOCK_CLIENT_SECRET"])
token_url: str = "https://api.flocksafety.com/oauth/token"
_token: str = field(default="", init=False, repr=False)
_expires_at: float = field(default=0.0, init=False)
def get_token(self) -> str:
"""Return a valid Bearer token, refreshing if within 60 seconds of expiry."""
if time.time() < self._expires_at - 60:
return self._token
response = requests.post(
self.token_url,
data={
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
},
timeout=10,
)
response.raise_for_status()
payload = response.json()
self._token = payload["access_token"]
self._expires_at = time.time() + payload["expires_in"]
return self._token
def auth_headers(self) -> dict:
return {"Authorization": f"Bearer {self.get_token()}"}
# Usage
auth = FlockAuthClient()
headers = auth.auth_headers()
Store FLOCK_CLIENT_ID and FLOCK_CLIENT_SECRET in your secret manager (AWS Secrets Manager, HashiCorp Vault, or at minimum a .env file excluded from version control). Never hardcode credentials.
Motorola Vigilant: CJIS-Compliant Token-Based Auth
Vigilant's API uses a session token obtained by POSTing credentials, but critically requires each request to carry an HMAC-SHA256 signature over the request body and timestamp to prevent replay attacks — a CJIS Security Policy requirement.
import os
import hmac
import hashlib
import time
import requests
VIGILANT_BASE_URL = os.environ["VIGILANT_BASE_URL"] # e.g. https://api.vigilant.agency.gov
VIGILANT_API_KEY = os.environ["VIGILANT_API_KEY"]
VIGILANT_SECRET = os.environ["VIGILANT_HMAC_SECRET"].encode()
def vigilant_signed_headers(body: bytes) -> dict:
"""
Generate HMAC-SHA256 signed headers required by Motorola Vigilant API.
CJIS requires timestamp + body signature to prevent replay attacks.
"""
timestamp = str(int(time.time()))
message = timestamp.encode() + body
signature = hmac.new(VIGILANT_SECRET, message, hashlib.sha256).hexdigest()
return {
"X-API-Key": VIGILANT_API_KEY,
"X-Timestamp": timestamp,
"X-Signature": signature,
"Content-Type": "application/json",
}
def vigilant_query(endpoint: str, payload: dict) -> dict:
import json
body = json.dumps(payload).encode()
headers = vigilant_signed_headers(body)
response = requests.post(
f"{VIGILANT_BASE_URL}{endpoint}",
headers=headers,
data=body,
timeout=15,
)
response.raise_for_status()
return response.json()
Vigilant session tokens expire after 8 hours by default (a CJIS-mandated maximum for unattended sessions). Flock's OAuth tokens expire after 3,600 seconds. In both cases, implement proactive refresh — check expiry before each request rather than catching 401s, which adds latency in production.
Data Models and Query Capabilities
Flock Safety Plate Read Schema
A Flock plate-read event contains: plate_number, state, timestamp (ISO 8601), camera_id, location (lat/lon), confidence (0.0–1.0), vehicle_make, vehicle_model, vehicle_color, vehicle_type, image_url, and alert_lists (matched hotlists). Confidence scores above 0.85 are considered high-confidence reads suitable for automated alerting.
Filtering and Paginated Queries
from dataclasses import dataclass
from typing import Optional
import requests
@dataclass
class PlateRead:
plate_number: str
state: str
timestamp: str
camera_id: str
latitude: float
longitude: float
confidence: float
vehicle_color: Optional[str]
vehicle_make: Optional[str]
def query_flock_reads(
auth_client,
min_lat: float, max_lat: float,
min_lon: float, max_lon: float,
start_time: str, end_time: str,
min_confidence: float = 0.85,
) -> list[PlateRead]:
"""
Paginated query to Flock Safety /v1/reads endpoint filtered by bounding box and time.
ISO 8601 timestamps required, e.g. '2025-01-15T00:00:00Z'
"""
base_url = "https://api.flocksafety.com/v1/reads"
results = []
cursor = None
while True:
params = {
"min_lat": min_lat,
"max_lat": max_lat,
"min_lon": min_lon,
"max_lon": max_lon,
"start_time": start_time,
"end_time": end_time,
"min_confidence": min_confidence,
"limit": 1000,
}
if cursor:
params["cursor"] = cursor
resp = requests.get(
base_url,
headers=auth_client.auth_headers(),
params=params,
timeout=30,
)
resp.raise_for_status()
data = resp.json()
# Sample response structure:
# {
# "reads": [
# {
# "plate_number": "ABC1234",
# "state": "CA",
# "timestamp": "2025-01-15T14:32:01Z",
# "camera_id": "cam_8f3a2b",
# "location": {"lat": 37.7749, "lon": -122.4194},
# "confidence": 0.97,
# "vehicle_color": "silver",
# "vehicle_make": "Toyota"
# }
# ],
# "next_cursor": "eyJpZCI6MTIzNH0=",
# "total_count": 4821
# }
for r in data.get("reads", []):
results.append(PlateRead(
plate_number=r["plate_number"],
state=r["state"],
timestamp=r["timestamp"],
camera_id=r["camera_id"],
latitude=r["location"]["lat"],
longitude=r["location"]["lon"],
confidence=r["confidence"],
vehicle_color=r.get("vehicle_color"),
vehicle_make=r.get("vehicle_make"),
))
cursor = data.get("next_cursor")
if not cursor:
break
return results
Field Naming Comparison: Flock vs Vigilant
| Concept | Flock Safety Field | Motorola Vigilant Field |
|---|---|---|
| Plate string | plate_number | LicensePlate |
| State/jurisdiction | state | PlateState |
| Read timestamp | timestamp | ReadDateTime (local TZ) |
| GPS latitude | location.lat | GPSLatitude |
| GPS longitude | location.lon | GPSLongitude |
| Confidence score | confidence (0.0–1.0) | Confidence (0–100 integer) |
| Camera identifier | camera_id | ReaderSerialNumber |
| Vehicle color | vehicle_color | VehicleColor |
Vigilant uses PascalCase throughout and expresses confidence as an integer percentage. Normalize both into a common schema before storing — this is exactly what the adapter pattern in Section 6 handles.
Webhook and Real-Time Event Streaming
Flock Safety Webhook Architecture
Flock Safety delivers plate-read events via HTTPS POST to your registered endpoint within 2–5 seconds of a camera read. Each payload is signed with HMAC-SHA256 using your webhook secret. The X-Flock-Signature header contains sha256=<hex_digest> computed over the raw request body.
// Node.js / Express webhook receiver for Flock Safety plate-read events
const express = require('express');
const crypto = require('crypto');
const redis = require('redis');
const app = express();
const redisClient = redis.createClient({ url: process.env.REDIS_URL });
redisClient.connect();
const FLOCK_WEBHOOK_SECRET = process.env.FLOCK_WEBHOOK_SECRET;
const IDEMPOTENCY_TTL_SECONDS = 86400; // 24 hours
app.use('/webhooks/flock', express.raw({ type: 'application/json' }));
app.post('/webhooks/flock', async (req, res) => {
// 1. Verify HMAC-SHA256 signature
const sigHeader = req.headers['x-flock-signature'];
if (!sigHeader || !sigHeader.startsWith('sha256=')) {
return res.status(401).json({ error: 'Missing signature' });
}
const expected = 'sha256=' + crypto
.createHmac('sha256', FLOCK_WEBHOOK_SECRET)
.update(req.body)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(sigHeader), Buffer.from(expected))) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(req.body.toString());
// 2. Idempotency deduplication via Redis
const idempotencyKey = `flock:read:${event.read_id}`;
const alreadyProcessed = await redisClient.set(
idempotencyKey,
'1',
{ NX: true, EX: IDEMPOTENCY_TTL_SECONDS }
);
if (!alreadyProcessed) {
// Redis returned null — key already existed, duplicate event
return res.status(200).json({ status: 'duplicate_ignored' });
}
// 3. Process the plate read event
console.log(`New plate read: ${event.plate_number} (${event.state}) at ${event.timestamp}`);
// → enqueue to your processing pipeline here
res.status(200).json({ status: 'accepted' });
});
app.listen(3000, () => console.log('Flock webhook receiver running on :3000'));
Motorola Vigilant: Polling Compensation Strategy
Vigilant has no native webhook support. You must poll the /reads/recent endpoint on a schedule. Use a 30-second polling interval as a baseline; tighter intervals risk hitting rate limits and require agency authorization. Store a last_seen_timestamp in Redis or your database and use it as the since parameter on each poll.
Real-Time Capability Comparison
| Metric | Flock Safety | Motorola Vigilant | |---|---|---| | Delivery mechanism | Push (webhooks) | Pull (polling) | | Typical latency | 2–5 seconds | 30–60 seconds (poll interval) | | Delivery guarantee | At-least-once | At-least-once (idempotent poll) | | Max events/second | ~50 (verify per contract) | Limited by poll rate | | Retry on failure | Flock retries 3x with backoff | You own retry logic entirely |
Compliance, Privacy, and Data Governance Integration
CJIS Security Policy Requirements
Both platforms handle CJIS-covered data when integrated into law enforcement workflows. CJIS Security Policy 5.9 requires: multi-factor authentication for system access, encryption in transit (TLS 1.2+) and at rest (AES-256), personnel security screening for anyone with access to CJIS systems, and a signed CJIS Security Addendum with your agency.
Flock Safety is increasingly signing CJIS Security Addendums with law enforcement partners, but its compliance posture is newer than Vigilant's. Motorola Vigilant has maintained CJIS compliance for over a decade and offers documented audit trails natively within the LEARN platform. If your integration serves law enforcement and involves direct access to CJIS-covered data, Vigilant's established compliance track record carries real operational and legal weight.
Compliance Checklist
| Requirement | Flock Safety | Motorola Vigilant | |---|---|---| | CJIS Security Addendum | ✓ (with LE partners) | ✓ (full, established) | | SOC 2 Type II | ✓ | ✓ | | CCPA data deletion support | ✓ (API endpoint available) | Varies by agency config | | GDPR-adjacent data residency | US-only deployment | US-only (LEARN) | | Audit log API access | Limited (contact sales) | Available to agency admins | | Data minimization support | Field filtering in queries | Requires query scoping | | Retention policy enforcement | Configurable 30–365 days | Agency-controlled |
Architecture Notes for Integrators
If you're building a commercial product on top of either API, you inherit compliance obligations your vendor does not automatically fulfill:
- Audit trails: Neither API logs your application's read patterns on your behalf. Implement append-only audit logs (CloudWatch Logs, Datadog, or a dedicated SIEM) recording: who queried, what plate/region, what timestamp, and what result was returned.
- Data minimization: Request only the fields you need. Both APIs support field selection parameters — use them. Don't cache full plate-read payloads if your use case only needs timestamps and plate strings.
- Deletion workflows: Flock offers a DELETE endpoint for specific reads. Vigilant deletion is handled at the agency level. If your product stores ALPR data derived from either source, you need a deletion pipeline that propagates to your own datastores when a request comes in.
- Legal exposure: Storing ALPR data that gets subpoenaed puts your company in the chain of custody. Consult counsel before caching any raw plate-read data from production.
SDK Support, Client Libraries, and Developer Experience
Flock Safety Developer Experience
Flock does not publish an official language-specific SDK, but does provide an OpenAPI 3.0 specification — which means you can generate a typed client in any language using tools like openapi-generator or fastapi-codegen. A Postman collection is available on request from Flock's developer relations team. The sandbox environment supports full CRUD operations against synthetic data.
Motorola Vigilant: SOAP Legacy + Modern REST
Vigilant's LEARN database queries historically ran over SOAP (WSDLs available from your Motorola account rep). The modern REST API covers most read/query operations, but some advanced LEARN features — historical correlation queries, multi-state hotlist federation — still require SOAP. This means your integration may need both a REST client and a SOAP client (use Python's zeep library for SOAP). Vigilant provides a Postman collection for REST endpoints but no OpenAPI spec.
Developer Experience Comparison
| Dimension | Flock Safety | Motorola Vigilant | |---|---|---| | OpenAPI / Swagger spec | ✓ | ✗ | | Postman collection | ✓ (on request) | ✓ (REST only) | | Sandbox environment | ✓ | ✗ | | Official SDK | ✗ | ✗ | | SOAP endpoints | ✗ | ✓ (legacy LEARN) | | HTTP status code consistency | High (standard 4xx/5xx) | Medium (some SOAP faults wrapped in 200s) | | API changelog / versioning | ✓ (semver, /v1/) | Limited |
TypeScript Unified PlateReadClient Adapter
import axios, { AxiosInstance } from 'axios';
import crypto from 'crypto';
interface PlateReadQuery {
startTime: string; // ISO 8601
endTime: string; // ISO 8601
minLat: number;
maxLat: number;
minLon: number;
maxLon: number;
minConfidence?: number;
}
interface NormalizedPlateRead {
plateNumber: string;
state: string;
timestamp: string;
latitude: number;
longitude: number;
confidence: number; // always 0.0–1.0
cameraId: string;
source: 'flock' | 'vigilant';
}
interface PlateReadClient {
query(params: PlateReadQuery): Promise<NormalizedPlateRead[]>;
}
// Flock Safety adapter
class FlockPlateReadClient implements PlateReadClient {
private http: AxiosInstance;
private token: string = '';
private tokenExpiry: number = 0;
constructor(
private clientId: string,
private clientSecret: string,
) {
this.http = axios.create({ baseURL: 'https://api.flocksafety.com' });
}
private async ensureToken(): Promise<void> {
if (Date.now() / 1000 < this.tokenExpiry - 60) return;
const resp = await this.http.post('/oauth/token', {
grant_type: 'client_credentials',
client_id: this.clientId,
client_secret: this.clientSecret,
});
this.token = resp.data.access_token;
this.tokenExpiry = Date.now() / 1000 + resp.data.expires_in;
}
async query(params: PlateReadQuery): Promise<NormalizedPlateRead[]> {
await this.ensureToken();
const reads: NormalizedPlateRead[] = [];
let cursor: string | undefined;
do {
const resp = await this.http.get('/v1/reads', {
headers: { Authorization: `Bearer ${this.token}` },
params: {
start_time: params.startTime,
end_time: params.endTime,
min_lat: params.minLat,
max_lat: params.maxLat,
min_lon: params.minLon,
max_lon: params.maxLon,
min_confidence: params.minConfidence ?? 0.85,
limit: 1000,
...(cursor ? { cursor } : {}),
},
});
for (const r of resp.data.reads) {
reads.push({
plateNumber: r.plate_number,
state: r.state,
timestamp: r.timestamp,
latitude: r.location.lat,
longitude: r.location.lon,
confidence: r.confidence,
cameraId: r.camera_id,
source: 'flock',
});
}
cursor = resp.data.next_cursor;
} while (cursor);
return reads;
}
}
// Motorola Vigilant adapter
class VigilantPlateReadClient implements PlateReadClient {
private http: AxiosInstance;
constructor(
private apiKey: string,
private hmacSecret: string,
baseUrl: string,
) {
this.http = axios.create({ baseURL: baseUrl });
}
private signedHeaders(body: string): Record<string, string> {
const timestamp = Math.floor(Date.now() / 1000).toString();
const signature = crypto
.createHmac('sha256', this.hmacSecret)
.update(timestamp + body)
.digest('hex');
return {
'X-API-Key': this.apiKey,
'X-Timestamp': timestamp,
'X-Signature': signature,
'Content-Type': 'application/json',
};
}
async query(params: PlateReadQuery): Promise<NormalizedPlateRead[]> {
const payload = JSON.stringify({
StartDateTime: params.startTime,
EndDateTime: params.endTime,
GPSBoundingBox: {
MinLatitude: params.minLat,
MaxLatitude: params.maxLat,
MinLongitude: params.minLon,
MaxLongitude: params.maxLon,
},
});
const resp = await this.http.post('/v2/reads/query', payload, {
headers: this.signedHeaders(payload),
});
return resp.data.Reads.map((r: any) => ({
plateNumber: r.LicensePlate,
state: r.PlateState,
timestamp: r.ReadDateTime,
latitude: r.GPSLatitude,
longitude: r.GPSLongitude,
confidence: r.Confidence / 100, // normalize to 0.0–1.0
cameraId: r.ReaderSerialNumber,
source: 'vigilant',
}));
}
}
// Factory: swap vendor without changing calling code
function createPlateReadClient(vendor: 'flock' | 'vigilant'): PlateReadClient {
if (vendor === 'flock') {
return new FlockPlateReadClient(
process.env.FLOCK_CLIENT_ID!,
process.env.FLOCK_CLIENT_SECRET!,
);
}
return new VigilantPlateReadClient(
process.env.VIGILANT_API_KEY!,
process.env.VIGILANT_HMAC_SECRET!,
process.env.VIGILANT_BASE_URL!,
);
}
This adapter pattern means you can run both vendors simultaneously, A/B test coverage, or migrate from one to the other without touching your processing pipeline.
When to Choose Flock Safety API
Flock Safety's API is the right choice when:
- You're building on a modern stack and need REST + JSON with an OpenAPI spec you can code-gen from. Flock's clean API surface reduces integration time significantly.
- Real-time alerting is core to your product. Flock's native webhook support means you can build sub-10-second plate-match alerting for HOA communities, university campuses, or smart city traffic management without polling overhead.
- Your deployment geography overlaps with Flock's camera network. Flock has aggressively expanded into suburban municipalities and private communities. If your users are in these areas, Flock data is dense and timely.
- You need a sandbox for development. Flock provides a test environment — invaluable when your team doesn't have production access to live camera networks during development.
- You're targeting the FBI procurement window. Flock's rapid nationwide camera deployment makes it a strong candidate for the federal ALPR contract reported in 2025/2026. Integrating now, while the API is pre-contract, gives you production-ready code before potential rate-limit or pricing changes hit.
- You're building for non-law-enforcement commercial use cases: smart city dashboards, logistics, parking enforcement, or campus security where CJIS compliance is not a hard requirement.
When to Choose Motorola Vigilant API
Motorola Vigilant is the correct choice when:
- Your customer is a law enforcement agency with an existing Motorola infrastructure footprint (CAD, RMS, body cameras). Vigilant integrates natively with Motorola PremierOne and related platforms — your integration lives in a familiar operational context.
- You need the LEARN database's breadth. Vigilant's LEARN platform aggregates plate reads from hundreds of agency and commercial sources nationwide. For investigative queries — finding where a plate appeared across multiple jurisdictions over weeks — LEARN has no equivalent.
- CJIS compliance is non-negotiable from day one. Vigilant has decade-long audit trails, signed Security Addendums with thousands of agencies, and a compliance team that knows the CJIS audit process inside out. For an agency that cannot risk a CJIS violation, Vigilant reduces organizational risk.
- You're integrating into an existing Motorola enterprise contract. If your agency already pays for Motorola Solutions products, Vigilant API access may be included or discounted. Switching to Flock means adding a new vendor relationship, procurement cycle, and security review.
- You can invest in integration complexity. If your team has time to handle SOAP endpoints, HMAC-signed requests, and manual polling pipelines, Vigilant's deeper data access may justify the engineering cost.
Verdict: Choosing the Right ALPR API for Your Integration
Final Weighted Scoring
| Dimension | Weight | Flock Safety | Motorola Vigilant | |---|---|---|---| | Ease of integration | 25% | 9/10 | 5/10 | | Real-time capability | 20% | 9/10 | 4/10 | | Compliance readiness (CJIS) | 20% | 6/10 | 9/10 | | Data coverage breadth | 20% | 6/10 | 9/10 | | Cost transparency | 10% | 7/10 | 4/10 | | SDK and tooling quality | 5% | 7/10 | 4/10 | | Weighted total | | 7.5/10 | 6.4/10 |
For the majority of commercial developers — smart city platforms, campus security, HOA tools, fleet management — Flock Safety API wins on developer experience, real-time delivery, and integration speed. It's the pragmatic choice for teams that need to ship.
For law enforcement agencies and government system integrators with existing Motorola infrastructure, Motorola Vigilant is the operational standard and the compliance-safe default. The LEARN database's national breadth and established CJIS track record justify the integration complexity when the use case demands them.
Regardless of which you choose, implement the adapter pattern shown in Section 6. Federal procurement activity around nationwide ALPR access could shift API terms, pricing, or availability on short notice. A unified PlateReadClient interface decouples your business logic from vendor specifics and lets you add, swap, or parallelize ALPR sources without rewriting your pipeline. That architectural investment pays for itself the first time a contract changes.