DRAFT — not indexed by search engines. Visible only via direct URL or the admin page.

OCPI Error Responses: Status Codes and How to Debug Them

A practical reference for OCPI status_code values (1xxx-4xxx), the response envelope, and how to debug the 2001, 3001, and 4001 errors you actually hit.

Every OCPI response — success or failure — arrives wrapped in the same JSON envelope, and inside that envelope is a four-digit status_code that tells you what actually happened. The trap is that this code is completely independent of the HTTP status. A partner can return 200 OK at the transport layer while the envelope says 2001, meaning your request was received and rejected. If you only check the HTTP status, you log a failed call as a success and the two sides drift apart until a reconciliation surfaces the gap.

The OCPI response envelope and status codes in the simulator

This is a reference and a debugging guide. Below is what each status_code range means, the specific codes you hit most often, and a repeatable method for going from “a partner returned an error” to “here is the field that was wrong.” If you want to see the envelope and these codes live rather than just read about them, try the interactive OCPI simulator — it lets you send well-formed and malformed messages and watch the exact status_code and status_message come back.

The response envelope

Every OCPI response body has the same shape, regardless of module or whether it succeeded:

{
  "data": { },
  "status_code": 1000,
  "status_message": "Success",
  "timestamp": "2026-07-12T09:41:00Z"
}

Four fields matter for debugging:

  • status_code — the OCPI-level result. This is the field that tells you what happened, not the HTTP status.
  • status_message — an optional but strongly recommended human-readable explanation. On errors, this is where the real information lives.
  • timestamp — when the responding system generated the message. Useful for ordering and for spotting clock drift between parties.
  • data — present on success, usually absent or empty on error.

The status_code is always a four-digit integer, and its first digit tells you which class of outcome you are dealing with. Everything else is a lookup.

The four ranges

OCPI groups status codes into four ranges by leading digit. Internalize the ranges first; the specific codes are secondary.

  • 1xxx — Success. The request was processed. 1000 is the generic success code and the one you see almost all the time. If you get a 1xxx, the operation worked; read data.
  • 2xxx — Client errors. You caused it. The request reached the partner but was malformed, incomplete, or referenced something unknown. Retrying the same request will fail the same way — you have to fix the request.
  • 3xxx — Server errors. The receiving party failed to process an otherwise valid request. This is their side breaking. Retrying with backoff or alerting the partner is the right response, not editing your payload.
  • 4xxx — Hub errors. A roaming hub sitting between you and the intended receiver raised the error. The receiver may be unknown to the hub, disconnected, or slow to respond. The fix lives in routing and connectivity, not in your message content.

That single mental model — my fault (2xxx), their fault (3xxx), the middle (4xxx) — resolves most of the “who do I go talk to” question before you even read the specific code.

The specific codes worth memorizing

You do not need to memorize the whole table, but a handful come up constantly.

Success

  • 1000 — Generic success. Nearly every good response.

Client errors (2xxx) — fix your request

  • 2000 — Generic client error. The catch-all when nothing more specific fits. Lean on status_message here.
  • 2001 — Invalid or missing parameters. The single most common error you will debug. A required field is absent, the wrong type, or malformed (a non-UTC timestamp, a lowercase country_code, a bad enum value).
  • 2002 — Not enough information. The request was structurally valid but lacked the data needed to act — for example, a real-time authorization request that does not carry enough to make a decision.
  • 2003 — Unknown Location. The referenced Location (or EVSE) is not known to the receiver.
  • 2004 — Unknown Token. The referenced Token is not known — common in authorization and Tokens-module flows.

Server errors (3xxx) — the partner’s side broke

  • 3000 — Generic server error. Something failed on their end.
  • 3001 — Unable to use the client’s API. The receiver tried to call back to your API and could not — often an auth, endpoint, or reachability problem on the return path.
  • 3002 — Unsupported version. The version you are speaking is not supported by the receiver.
  • 3003 — No matching endpoints, or expected endpoints are missing. Surfaces during the Versions and Credentials exchange when the two endpoint lists do not line up.

Hub errors (4xxx) — routing between parties

  • 4000 — Generic hub error.
  • 4001 — Unknown receiver. The to party the hub was asked to route to is not known to the hub.
  • 4002 — Timeout on a forwarded request. The hub forwarded your message but the receiver did not answer in time.
  • 4003 — Connection problem. The receiving party is not currently connected to the hub.

How to debug an OCPI error, step by step

When a partner returns an error, resist the urge to immediately change your payload. Work the layers in order.

1. Read the envelope, not the HTTP status. Confirm whether the failure is at the transport layer (a real HTTP 4xx/5xx, meaning the call may not have been processed at all) or an OCPI-level rejection carried inside a 200. These are different problems. A common source of confusion is an HTTP 401/403, which is an authentication failure at the transport layer — covered in OCPI security and authentication (coming soon) — versus an OCPI 2xxx inside a 200, which means you got past auth and the payload itself was rejected.

2. Classify by the leading digit. 2xxx means stop and inspect your request. 3xxx means the partner’s system failed — capture the evidence and open a ticket with them rather than mutating your message. 4xxx means talk to the hub about routing or connectivity.

3. Read status_message carefully. This free-text field is where a decent implementation tells you the actual cause: invalid country_code: must be ISO 3166-1 alpha-2 uppercase, or missing required field: connector_id. If the message is empty or useless, that itself is a finding — note which partner ships bare error codes so you know to lean on your own request logs when debugging with them.

4. Reproduce in isolation. Take the exact rejected payload and replay it against a controlled target. This is where the OCPI simulator earns its keep: send the same message, tweak one field at a time, and watch the status_code change from 2001 back to 1000. Reproducing against a mock removes partner availability and partner-side noise from the equation, so you are testing your payload and nothing else.

5. Confirm the fix and add a regression test. Once you know the offending field, add a test that sends the previously-broken shape and asserts the correct code. Error paths deserve tests as much as happy paths — a point covered in more depth in the OCPI integration pitfalls (coming soon) checklist.

The error responses you send matter too

Debugging is bidirectional. When you are the receiver and you reject a request, the error you return determines how fast the other party can fix their side. Two habits separate good implementations:

Return the right code, not a blanket 3000. If the request is missing a parameter, that is a 2001 (client error), not a server error. Miscategorizing your own errors sends partners chasing the wrong side. A malformed field is their bug to fix; returning 3000 implies it is yours.

Always populate status_message with something actionable. invalid or missing parameters is barely better than nothing. missing required field: party_id lets the partner fix it without opening a ticket. Include enough for them to act, without leaking internal stack traces, database errors, or infrastructure detail. The difference between a good and a bad error message is often the difference between a five-minute fix and a three-day support thread.

One caution: distinguish a real error from rate limiting. A burst of 3000s or transport-level 429s under load may not be a bug in your payload at all but a throttle kicking in — the mechanics of which are covered in OCPI rate limiting and abuse protection (coming soon). Backing off is the fix there, not re-crafting the message.

Where this fits

If you are new to how these messages flow between roaming parties in the first place, start with what OCPI is for the overall picture of CPOs, eMSPs, and hubs — whether that is a North American network like ChargePoint, EVgo, or Electrify America, or a European one like IONITY or Allego. The error model is the same across all of them: one envelope, one status_code, four ranges.

The fastest way to build intuition is to make the errors happen on purpose. Open the OCPI simulator, send a clean request to see 1000, then break it deliberately — drop a required field, lowercase a country_code, send a local-time datetime — and watch the exact code and message the receiver returns. Once you have triggered 2001, 2003, and a version mismatch yourself, reading them in production logs becomes routine instead of a puzzle.

Key takeaways

  • OCPI carries its own status_code in the response envelope, independent of the HTTP status. HTTP 200 with status_code 2001 means received and rejected — always read the envelope.
  • The leading digit classifies the failure: 1xxx success, 2xxx your request, 3xxx the partner’s server, 4xxx the hub. That alone tells you who to go fix.
  • 2001 (invalid or missing parameters) is the error you will debug most; the specific offending field lives in status_message.
  • When you send errors, return the correct range and a specific, actionable status_message — it directly determines how fast the other side can fix things.
  • Reproduce rejected payloads in isolation. The interactive simulator lets you trigger and resolve each code one field at a time.

Frequently asked questions

What does OCPI status_code 2001 mean?

2001 is "invalid or missing parameters" — a client error. Your request reached the partner and was rejected because a field was absent, malformed, or the wrong type. Read the status_message for the specific field. It is a fix-the-request error, not something to retry unchanged.

Why did I get HTTP 200 but the request failed?

OCPI carries its own status_code inside the JSON response envelope, independent of the HTTP status. HTTP 200 only confirms the transport delivered your call. If the envelope status_code is in the 2xxx, 3xxx, or 4xxx range, the request was rejected or errored. Always parse the envelope, never trust the HTTP code alone.

What is the difference between 3xxx and 4xxx errors?

3xxx codes are server errors on the party you called — their system failed to process a valid request (retry or alert them). 4xxx codes are hub errors raised by a roaming hub sitting between you and the receiver, such as 4001 unknown receiver or 4002 timeout on a forwarded request. The fix differs: a 3xxx points at the partner, a 4xxx points at routing.

Is status_message required in an OCPI error response?

The status_message is optional in the spec but strongly recommended, especially for errors. A bare error code with no message forces the other side to guess. Good implementations always populate it with an actionable, human-readable reason without leaking internal state.

Found this useful? Share it.