Getting Started with Levy Compliance
This article walks you through onboarding a single jurisdiction (one city) end-to-end. The end state: MDS endpoints are live, the city's Policy feed is being polled every 60 seconds, geofences activate on schedule, and the city contact receives daily/weekly/monthly digest emails.
Prerequisites
Before you begin, confirm the following:
| Requirement | Where to check |
|---|---|
| You hold an active operator permit (or are in the application phase) | City permit office |
Your fleet has at least one vehicle with iot_imei set | /dashboard/vehicles |
| Your subaccount has an MDS bearer token issued | Settings -> API & Integrations |
| You have the city's Policy feed URL (and auth token, if private) | City contact |
| You have at least one city contact email for digest delivery | City contact |
MDS bearer tokens
Levy Compliance still uses the existing bearer-token table (mds_city_tokens) for primary auth. The new JWT layer is signed on top of every response — cities can either continue using bearer tokens or verify the JWT against your JWKS URL. See JWKS Key Management.
Step 1 — Create the jurisdiction
Open the compliance dashboard
Go to Dashboard -> Compliance at /dashboard/compliance. You will see a jurisdictions index (empty if this is your first one) plus a conflict alert banner at the top.
Click 'Add jurisdiction'
Navigates to /dashboard/compliance/new. The form fields map 1:1 to the mds_jurisdictions table.
Fill in the form
| Field | Example | Notes |
|---|---|---|
| Name | Boulder, CO | Display name shown to operators and cities. |
| Slug | boulder-co | Used in the public URL: /city/boulder-co. Lowercase, dash-separated, must be unique. |
| Policy feed URL | https://populus.cityofboulder.org/mds/policy | The city's published Policy endpoint. |
| Policy feed auth token | bearer xyz... | Optional. Stored encrypted. |
| Poll interval (s) | 60 | Default 60s. Cities with high update churn can go lower; quiet cities can go higher. |
| Active | true | Disable to pause polling without deleting. |
Save
The jurisdiction is created. The first JWKS keypair is lazy-minted the next time anyone hits /api/mds/{subaccountId}/.well-known/jwks.json — you can trigger it now by opening that URL in a browser.
Step 2 — Confirm MDS endpoints respond
Each jurisdiction inherits the same MDS endpoint base under your subaccount:
GET /api/mds/{subaccountId}/provider/v2/vehicles
GET /api/mds/{subaccountId}/provider/v2/vehicles/status
GET /api/mds/{subaccountId}/provider/v2/trips
GET /api/mds/{subaccountId}/provider/v2/events
GET /api/mds/{subaccountId}/provider/v2/telemetry/{vehicle_id}
GET /api/mds/{subaccountId}/provider/v2/stops
GET /api/mds/{subaccountId}/provider/v2/reports
GET /api/mds/{subaccountId}/.well-known/jwks.json
Hit vehicles/status with your bearer token to confirm:
curl -H "Authorization: Bearer <YOUR_MDS_TOKEN>" \
https://fleets.levyelectric.com/api/mds/<subaccountId>/provider/v2/vehicles/status
A successful response carries:
- HTTP 200
Content-Type: application/vnd.mds+json;version=2.0MDS-JWTheader (signed proof of the response body sha256)- A
versionfield set to2.0.1in the JSON envelope
See MDS Provider Setup for the full endpoint reference.
Step 3 — Hand the URLs to the city
Send the city contact a short email with these four pieces:
| Item | Value |
|---|---|
| MDS Provider base | https://fleets.levyelectric.com/api/mds/<subaccountId>/provider/v2/ |
| JWKS URL | https://fleets.levyelectric.com/api/mds/<subaccountId>/.well-known/jwks.json |
| Bearer token | (issue from Settings -> API & Integrations) |
| GBFS 3.0 root | https://fleets.levyelectric.com/api/gbfs/v3/<subaccountId>/gbfs.json |
The city's Populus / Ride Report / Lacuna instance will validate the endpoints against the MobilityData GBFS validator and the OMF mds-provider-validator before flagging your fleet as live on their dashboard. Both validators should pass with no warnings.
Step 4 — Watch the first Policy poll
The mds-policy-poll cron runs every minute. Within 60 seconds of saving the jurisdiction:
- The poller hits the city's Policy feed.
- The raw payload is sha256-hashed and compared to the last-known hash.
- If it changed, the Zod validator parses
policies[]andgeographies[]. - Policies are upserted; rules are replaced wholesale;
policy_geofencesrows are materialized from the referenced geographies. - A row lands in
mds_policy_auditwithrun_id,diffJSON, andapplied_at.
Open /dashboard/compliance/{jurisdiction-id} and you should see the active policies list populated. See Policy Ingestion from Cities for the diff viewer.
Don't see any policies?
Three common causes: (1) the city's feed URL is wrong or behind auth you didn't configure, (2) the feed validates but contains no policies for your fleet's vehicle types, or (3) the feed responded but its schema doesn't match MDS 2.0. The audit log records the parse error; check there first.
Step 5 — Wire up city contacts
City contacts get magic-link access to the City Portal and receive digest emails on a cadence you set.
From the jurisdiction page, click 'Add city contact'
Adds a row to city_contacts scoped to this jurisdiction.
Set name, email, role, and digest_frequency
| Field | Example |
|---|---|
sarah@bouldercolorado.gov | |
| Name | Sarah Johnson |
| Role | City Compliance Officer |
| Portal access | true |
| Digest frequency | weekly (options: daily, weekly, monthly) |
Save and notify the contact
The contact can now go to /city/{slug}, enter their email, and receive a magic link. They will also start receiving digest emails on the chosen cadence.
Step 6 — Define permit conditions
If your permit specifies enforceable conditions (fleet cap, equity-zone deployment percentage, complaint SLA), record them in permit_conditions. The compliance reporter evaluates them on each digest run.
condition_type | What it checks |
|---|---|
fleet_cap | COUNT(active vehicles in jurisdiction) <= cap |
equity_zone_pct | % of deployed fleet inside equity geographies >= threshold |
complaint_sla | Each complaint's responded_at within N hours of created_at |
trip_report_eod | A trip CSV is generated daily at 23:59 jurisdiction-local time |
corral_utilization | Hourly snapshot of parking corral occupancy |
See Permit-Condition Reports for the evaluator details.
Step 7 — Verify the first digest
The compliance-digest cron runs hourly and walks every city_contacts row whose cadence window has elapsed. For a daily contact created today, the first email lands within an hour after the digest period closes (typically midnight in the jurisdiction's local time).
When the email goes out:
- A row is written to
city_compliance_reportswithpayload(the rendered JSON) andpdf_urlif a monthly PDF was attached. - The contact's
last_digest_sent_atis updated.
If the city doesn't see the email after the expected window, check spam, then check last_digest_sent_at in city_contacts — if it's stale, the cron didn't fire or the sender errored. See Troubleshooting.
What you've accomplished
After steps 1-7:
- Your subaccount publishes a conformant MDS 2.0 Provider feed
- A GBFS 3.0 feed (with
vehicle_type_idper zone rule) is live alongside the legacy 2.x feed - The city's Policy feed is polled every minute and diffed against last-known
- Stacked geofences with the correct priority materialize from city rules
- The city contact can log in to the portal via magic link
- Digest emails will flow on the configured cadence
- Permit-condition pass/fail is computed daily and shown in both the operator dashboard and city portal
You can now point your city permit officer at the public MDS endpoints and the GBFS 3.0 root, and let validators do their thing.
What's next
- MDS Provider Setup — endpoint reference and signing details.
- Policy Ingestion from Cities — diff viewer, audit, activation.
- Stacked Geofence Priority — how city rules override your operator zones.
- Troubleshooting — when polls or digests don't fire.