ProRankedDashboard ↗

Build with an AI agent

You don't have to write this integration by hand. Point a coding agent at ProRanked, give it one of the prompts below, and it will read the spec and build against the real API.

This page is the short version of how to do that well.

1. Get a key

Create one at app.proranked.com/api-keys. The dialog that appears when the key is created includes a Prompt for coding agents — copy it, paste it into your agent, and it has everything it needs to start. Copy the key separately; it's shown once.

Which key:

BuildingSurfaceKey
A driver app — map, sessions, paymentseMSPPublishable pr_pk_… (browser-safe)
An operator console — chargers, tariffs, billingCPMSSecret pr_sk_… (server-side) or Connect OAuth for a browser app

2. Fork a starter

Both are complete, working apps. Forking one and re-branding it is dramatically faster than starting from an empty folder — and it means the agent has a correct, typed API client to copy from.

RepoWhat it is
proranked-emsp-starterDriver app — map, charger detail, sessions, payments. React + Vite. One typed client covering ~46 /emsp/v1 endpoints.
proranked-cpms-starterOperator console — chargers, sessions, tariffs, webhooks, billing. Backend-less: browser OAuth + PKCE, no secret to hold.

Each ships a CLAUDE.md (auto-loaded by Claude Code — the API contract and the mistakes to avoid) and a PROMPTS.md (copy-paste prompts for re-branding, adding pages, and wiring flows).

Use a coding agent, not a chat artifact. Claude Artifacts run under a content-security policy that blocks requests to api.proranked.com, so an artifact can only ever mock this API. Artifacts are great for exploring a look; use Claude Code, Cursor, or similar to build the real thing.

3. The starting prompt

If you don't have the one from the key dialog, this is it:

You have access to the ProRanked EV-charging platform API.
The llms.txt file is a very good starting point. Read it first, then go from there
based on what the user needs.

llms.txt (overview + all doc links):  https://docs.proranked.com/specs/llms.txt
OpenAPI (eMSP):                       https://docs.proranked.com/specs/openapi-emsp.json
OpenAPI (CPMS):                       https://docs.proranked.com/specs/openapi-cpms.json

Base URL:  https://api.proranked.com
           Paths are /emsp/v1/... and /cpms/v1/... — there is NO /api prefix.
Auth:      X-API-Key: <key>   (header on every request)
My key is in the environment as PRORANKED_SECRET_KEY — read it from there, never
hardcode it, and never commit it.

Starter repo to fork:
  https://github.com/ProRanked/proranked-cpms-starter   (operator console)
  https://github.com/ProRanked/proranked-emsp-starter   (driver app)

Before writing any UI, verify the key works:
  curl https://api.proranked.com/cpms/v1/me -H "X-API-Key: $PRORANKED_SECRET_KEY"

Do not invent endpoints and do not mock the API. If something you need isn't in the
OpenAPI spec, or you get a 401/403, stop and tell me instead of working around it.

Where to paste it

Every prompt on this page works in any coding agent. What differs is the conventions file each one reads — the starters ship both CLAUDE.md and AGENTS.md, so two of these need no setup and two need one line.

Claude Code

Run `claude` in the repo and paste the prompt at the prompt.

Reads CLAUDE.md automatically — the starters ship one, so it already knows the base URL, the auth model and the mistakes to avoid.

export PRORANKED_SECRET_KEY="pr_sk_test_…"
cd proranked-cpms-starter && claude

Codex

Paste into the Codex CLI or the Codex task box.

Reads AGENTS.md automatically — the starters ship one alongside CLAUDE.md with the same contract.

export PRORANKED_SECRET_KEY="pr_sk_test_…"
cd proranked-cpms-starter && codex

Cursor

Open Composer (⌘I) — not inline edit (⌘K), which is scoped to one file.

Does not read CLAUDE.md on its own. Add “Follow the rules in CLAUDE.md.” to the prompt, or save that file as a project rule so it applies to every request.

mkdir -p .cursor/rules && cp CLAUDE.md .cursor/rules/proranked.mdc

VS Code

Copilot Chat in Agent mode, with the repo open as your workspace.

Does not read CLAUDE.md on its own. Either reference it in chat with #CLAUDE.md, or copy it to .github/copilot-instructions.md so it loads on every request.

mkdir -p .github && cp AGENTS.md .github/copilot-instructions.md

Use a coding agent, not a chat artifact. Claude Artifacts run under a content-security policy that blocks requests to api.proranked.com, so an artifact can only ever mock this API.

Prompt library

Edit the bracketed parts, then paste. Each one encodes a failure mode we've watched agents hit — they're the same prompts the dashboard shows, and the same ones each starter repo ships in its PROMPTS.md.

Operator console (CPMS)

Fork and re-brand the operator console — A complete working console in your colours, no backend to host.

Fork https://github.com/ProRanked/proranked-cpms-starter and re-brand it as [YOUR BRAND].

Brand:           [YOUR BRAND]
Primary colour:  [#HEX]
Feel:            [e.g. dark, dense, control-room]

Read CLAUDE.md first. Re-brand through the design tokens — and note the brand colour
appears in THREE notations, so check all of them: the token file, the chart colours, and
the rgba() tints used by the page gradients, glow shadow and scrollbar.

Before deploying anywhere other than localhost, register your own Connect OAuth client:
the bundled VITE_OIDC_CLIENT_ID is a shared reference client with fixed redirect URIs,
so a real deployment fails login with invalid_request / ID2043. Register at
app.proranked.com → OAuth clients, add your redirect URI and origin, and use the
pr_spa_ client id you get back. Allow ~60s for it to propagate.

Keep every API call as-is. Tell me which files to edit to adjust the look further.

Operator dashboard from scratch — No fork — build the console you actually want.

Build an operator dashboard on the ProRanked CPMS API.

Read https://docs.proranked.com/specs/llms.txt, then
https://docs.proranked.com/specs/openapi-cpms.json.

What I want on it: [e.g. "live charger status across all sites, today's sessions and
revenue, and an alert list"].

Base URL: https://api.proranked.com — paths are /cpms/v1/... with NO /api prefix.
Auth: X-API-Key header. My secret key is in the environment as PRORANKED_SECRET_KEY.

FIRST call, before any UI:
  curl https://api.proranked.com/cpms/v1/me -H "X-API-Key: $PRORANKED_SECRET_KEY"
That returns my networks and the scopes this key actually has. Build only what those
scopes allow, and disable (don't hide) anything they don't — asking for a scope does not
grant it, and a Start button that 403s is worse than no Start button.

Note a new network is empty, so list endpoints correctly return {"data": []}. That is
NOT a broken key — do not switch to mock data because a list came back empty.

Remote start / stop a charger — Live control. Read the elevated-scope warning first.

Add remote start/stop (and reboot) control for chargers to this console.

Before writing UI: cpms:command:chargers is an ELEVATED scope and is deliberately NOT in
the default set for a new key. Call GET /cpms/v1/me and check whether this key has it.

  - If it does: build the controls.
  - If it does NOT: build them DISABLED with a tooltip explaining the key lacks
    cpms:command:chargers, and tell me to re-issue the key with elevated scopes from
    app.proranked.com → API keys. Do not work around it and do not mock the call.

These commands act on physical hardware. Require an explicit confirm step, show the
command's real result (they're asynchronous — surface the accepted/rejected response
rather than assuming success), and never fire one on page load or on hover.

Webhooks for session events — React to charging events instead of polling.

Set up webhooks so we react to charging events instead of polling.

Read the webhooks section of https://docs.proranked.com/specs/openapi-cpms.json first.

Two things that trip people up, both real:
  1. The session events are charging.session.started / .stopped / .updated. There is NO
     "session.completed" — fetch the event-type list from the API rather than
     hardcoding it, and tell me if what I asked for isn't in it.
  2. On create, "events" is a COMMA-SEPARATED STRING, not an array.

Build the receiving endpoint too: verify the HMAC signature on every delivery (the
secret is shown once at creation — read it from the environment), return 2xx fast, and
process asynchronously. Make it idempotent; deliveries can repeat.

Then show me how to replay a failed delivery from the dashboard.

Bulk-provision a network — Create locations, chargers and tariffs from a spreadsheet.

Write a script that provisions a whole network from [CSV/spreadsheet at PATH]:
locations, then chargers under them, then tariffs, then assign tariffs to connectors.

Read https://docs.proranked.com/specs/openapi-cpms.json and follow the real creation
order and required fields — tell me what the file is missing BEFORE you start writing.

Requirements:
  - Idempotent: safe to re-run without creating duplicates. Match on the external
    identifier, don't blindly POST.
  - Dry-run mode that prints exactly what it would create, and is the default.
  - Stop on the first unexpected error with the failing row and the API response —
    do not silently continue past failures.
  - Respect rate limits: check the response headers and back off rather than hammering.

My key is in the environment as PRORANKED_SECRET_KEY.

Driver app (eMSP)

Build a branded driver app — Fork the starter and re-skin it. The fastest path to something real.

Fork https://github.com/ProRanked/proranked-emsp-starter and re-brand it as [YOUR BRAND].

Brand:           [YOUR BRAND]
Primary colour:  [#HEX]
Tagline:         [YOUR TAGLINE]
Feel:            [e.g. clean and technical / warm and friendly]

Read CLAUDE.md first — it says where the design tokens live and which files carry the
brand. Do it through those, not screen by screen.

Two things that fail silently, so check them:
  1. In .env, QUOTE the hex values (VITE_BRAND_PRIMARY="#f5a524"). Unquoted, the # is
     read as a comment, the value arrives empty, and the app builds in the DEFAULT
     brand with no error anywhere.
  2. index.html carries the reference title until you set VITE_BRAND_NAME — that's what
     the browser tab, bookmarks and link previews show.

Keep every API call and screen behaviour exactly as-is. When you're done, run the dev
server and tell me which files to edit to tweak the look further.

Wire the full charging flow — Find a charger → authorize the driver → start → live meter → stop → receipt.

Wire the complete charging journey end to end in this app: find a charger, sign the
driver in, start a session, show live meter values, stop, and show the receipt.

Read the OpenAPI spec at https://docs.proranked.com/specs/openapi-emsp.json and use the
existing typed client — only add a method if the endpoint genuinely isn't covered.

The rule agents get wrong here: auth is TWO-PART. The API key alone reads public data
(locations, chargers, tariffs). Driver actions — start, stop, account, payments — ALSO
need the driver's JWT from POST /emsp/v1/auth/driver/login, sent as
"Authorization: Bearer <jwt>" alongside the key. Key-only requests 403 on those.

Handle the money path explicitly: a driver with no saved card gets 402
payment_method_required on start. Show the add-a-card flow, don't swallow it.

Do not mock anything. If an endpoint you need isn't in the spec, stop and tell me.

Add charging to an app you already have — No fork — bolt the eMSP API onto an existing product.

Add EV charging to this existing app using the ProRanked eMSP API.

Start by reading https://docs.proranked.com/specs/llms.txt, then the eMSP OpenAPI spec.

Scope: [WHAT YOU WANT, e.g. "a map of nearby chargers and the ability to start/stop a
charge from inside our existing account area"].

Base URL: https://api.proranked.com — paths are /emsp/v1/... with NO /api prefix.
Auth: X-API-Key header. My publishable key is in the environment as
PRORANKED_PUBLISHABLE_KEY — read it from there, never hardcode it.

Driver actions additionally need the driver's JWT (POST /emsp/v1/auth/driver/login) as
a Bearer token. Map that onto our existing user accounts — ask me how our auth works
before you design that part rather than guessing.

Match the conventions already in this codebase. Verify the key first with
  curl https://api.proranked.com/emsp/v1/me-key -H "X-API-Key: $PRORANKED_PUBLISHABLE_KEY"

QR pay-per-charge, no account — Scan a code, pay, charge. No sign-up flow at all.

Build a guest "scan and charge" flow: the driver scans a QR code on the charger, pays,
and charges — with no account creation.

Read https://docs.proranked.com/specs/openapi-emsp.json first and tell me which
endpoints support a guest/anonymous session before you write any UI. If a true guest
flow isn't available on this API, say so and propose the closest supported alternative
(e.g. a lightweight driver record created behind the scenes) — do NOT invent endpoints.

The QR should encode the charger's identifier; resolve it to a charger and connector,
show the tariff and the pre-authorisation amount BEFORE taking payment, then start.

Handle 402 payment_method_required and the pre-auth decline path explicitly — those are
the two states a real driver actually hits.

Either surface

Explore what my key can do — A safe first prompt when you are not sure what to build yet.

Explore the ProRanked API for me and report back — don't build anything yet.

Read https://docs.proranked.com/specs/llms.txt first.

Then, using my key from the environment:
  1. Call the key self-description endpoint for its family
     (/cpms/v1/me for a CPMS key, /emsp/v1/me-key for an eMSP key) and tell me the
     family, environment (test or live), and exactly which scopes I have.
  2. Fetch the scope catalog at /.well-known/scopes on that family and tell me what I
     do NOT have that I might want.
  3. Skim the OpenAPI spec for my family and give me a short list of what I could
     realistically build with these scopes, roughly hardest to easiest.

Do not mock anything and do not write application code in this pass. If a call fails
with 401 or 403, show me the exact response instead of working around it.

Let the agent describe its own key

Every key can introspect itself. This is the right first call for an agent — it works on a brand-new account, where list endpoints return an empty array that looks identical to a bad key.

GET /emsp/v1/me-key      # eMSP  -> family, environment, visibility, scopes, capabilities
GET /cpms/v1/me          # CPMS  -> networks, scopes, key type

Both also report the rate-limit and idempotency conventions, and link to the scope catalog:

GET /emsp/v1/.well-known/scopes
GET /cpms/v1/.well-known/scopes

Keys are family-bound: an eMSP key on a /cpms/v1 path returns 403 wrong_api_family, and vice-versa. Point the agent at its own family's endpoint.

What agents get wrong

Four things account for most wasted sessions. Put them in your prompt.

An empty list is a success. A new account has no chargers, locations or sessions, so GET /cpms/v1/chargers correctly returns {"data": []}. That looks identical to a broken key, and it's the most common reason an agent quietly switches to mock data. Use GET /cpms/v1/me to prove a credential works — it always returns your networks and scopes.

eMSP auth is two-part. The API key alone reads public data (locations, chargers, tariffs). Driver actions — start/stop a charge, account, payments — also need the driver's JWT from POST /emsp/v1/auth/driver/login as Authorization: Bearer …. A key-only request returns 403 there.

Asking for a CPMS scope doesn't grant it. Scopes are intersected server-side with the operator's role on the network: Viewer gets reads, Operator adds writes, Admin adds commands. And cpms:command:chargers (remote start/stop/reboot) is elevated — excluded from the default set. An agent will happily build a Start button that 403s. Have it check /cpms/v1/me first and disable what isn't granted.

Webhook event names. The session events are charging.session.started / .stopped / .updated. There is no session.completed, and connector status is connector.status_changed, not charger.status_changed. Fetch the list from GET /cpms/v1/webhooks/event-types rather than hardcoding, and note that events on create is a comma-separated string, not an array.

Verifying a delivery. Every delivery carries X-Webhook-Signature, which is sha256= + the lowercase hex HMAC-SHA256 of the raw request body keyed with the subscription's hmac_secret (shown once, at creation). Deliveries also carry X-Webhook-Event and X-Webhook-Delivery. Compare signatures with a constant-time function, not ==.

Machine-readable everything

Point your agent at these directly: