SkyV2X/v2json

REST API

Integrate the V2X codec.

Decode a hex UPER message to JSON, or encode JSON back to the wire. One POST endpoint, both directions, no SDK. Copy-paste examples below.

The endpoint

POST https://v2json.skyv2x.com/api/v1/decode
FieldTypeDefaultNotes
hexstringrequiredUPER message as hex. Spaces are ignored.
typestringautoAuto-detects by messageID, or force one: cam, denm, cpm, vam, mapem, spatem, srem, ssem, ivim, rtcmem.
enrichbooleantrueAdds readable sibling fields (enum names, decoded values, units). Set false for raw ETSI JSON.

Rate limit: 120 requests/minute per IP. Response: 200 with { message_type, metadata, payload }.

Examples

Each call decodes the same real Collective Perception Message (CPM). Swap the hex for your own.

curl
curl -s -X POST https://v2json.skyv2x.com/api/v1/decode \
  -H 'Content-Type: application/json' \
  -d '{"hex":"020E0001869F028251F6C74A962F0B0365833700320190000D6D83100300001241E008058010000C00603E80638032018E0000063E045897FFE25FFF890419E0","type":"auto","enrich":true}'
Python — requests
import requests

resp = requests.post(
    "https://v2json.skyv2x.com/api/v1/decode",
    json={
        "hex": "020E0001869F028251F6C74A962F0B0365833700320190000D6D83100300001241E008058010000C00603E80638032018E0000063E045897FFE25FFF890419E0",
        "type": "auto",     # or "cpm"
        "enrich": True,      # False for raw ETSI JSON
    },
    timeout=5,
)
resp.raise_for_status()
data = resp.json()
print(data["message_type"])   # CPM
print(data["payload"])        # decoded message
JavaScript — fetch (browser)
const res = await fetch("https://v2json.skyv2x.com/api/v1/decode", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    hex: "020E0001869F028251F6C74A962F0B0365833700320190000D6D83100300001241E008058010000C00603E80638032018E0000063E045897FFE25FFF890419E0",
    type: "auto",
    enrich: true,
  }),
});
const data = await res.json();
console.log(data.message_type, data.payload);
Node.js — native fetch (18+)
// Node 18+ ships fetch natively; no dependency needed.
const res = await fetch("https://v2json.skyv2x.com/api/v1/decode", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ hex: process.argv[2], type: "auto", enrich: true }),
});
if (!res.ok) throw new Error(`decode failed: HTTP ${res.status}`);
const { message_type, payload } = await res.json();
console.log(message_type, JSON.stringify(payload, null, 2));

The response

The decode lives under payload; metadata carries the decode status and sizing.

200 · application/json
{
  "message_type": "CPM",
  "metadata": {
    "decode_status": "success",
    "decoded_bytes": 64,
    "message_type": "CPM",
    "encoding": "UPER",
    "tool_version": "..."
  },
  "payload": {
    "header": { "protocolVersion": 2, "messageId": 14, "stationId": ... },
    "payload": { "...": "decoded CPM containers" }
  }
}

With enrich: true, readable siblings sit next to the coded fields: enum names, GNSS in 7-decimal degrees, ETSI unavailable markers as explicit null. Send enrich: false for raw ETSI JSON.

Encode JSON back to the wire

Send a decoded message as JSON, get the UPER hex back. The input is the bare message: the payload from a decode with enrich: false, no { metadata, message } wrapper, no _* keys.

POST https://v2json.skyv2x.com/api/v1/encode
FieldTypeDefaultNotes
payloadobjectrequiredThe bare decoded message (raw, no _* keys).
typestringrequiredExplicit — no auto. CAM needs a variant, cam_en or cam_ts (they share messageID 2); others are denm, cpm, vam, mapem, spatem, srem, ssem, ivim, rtcmem.

Response: 200 with { hex, metadata }. An unchanged message round-trips byte-for-byte. A value outside the ASN.1 range is rejected (400) rather than producing invalid hex.

Decode, edit, encode. The round-trip in each language:

curl
HEX=020E0001869F028251F6C74A962F0B0365833700320190000D6D83100300001241E008058010000C00603E80638032018E0000063E045897FFE25FFF890419E0

# decode → take the raw message → encode it back
MSG=$(curl -s -X POST https://v2json.skyv2x.com/api/v1/decode \
  -H 'Content-Type: application/json' \
  -d "{\"hex\":\"$HEX\",\"type\":\"auto\",\"enrich\":false}" | jq -c '.payload')

curl -s -X POST https://v2json.skyv2x.com/api/v1/encode \
  -H 'Content-Type: application/json' \
  -d "{\"payload\":$MSG,\"type\":\"cpm\"}" | jq -r '.hex'
Python — requests
import requests
API = "https://v2json.skyv2x.com/api/v1"
hex_in = "020E0001869F028251F6C74A962F0B0365833700320190000D6D83100300001241E008058010000C00603E80638032018E0000063E045897FFE25FFF890419E0"

# 1 · decode to the raw message (no _* enrichments)
msg = requests.post(f"{API}/decode",
    json={"hex": hex_in, "type": "auto", "enrich": False}, timeout=5).json()["payload"]

# 2 · edit msg here …

# 3 · encode it back to hex
hex_out = requests.post(f"{API}/encode",
    json={"payload": msg, "type": "cpm"}, timeout=5).json()["hex"]
print(hex_out)   # identical to hex_in if msg is unchanged
JavaScript — fetch (browser)
const API = "https://v2json.skyv2x.com/api/v1";
const post = (p, body) => fetch(`${API}/${p}`, {
  method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body),
}).then((r) => r.json());

const { payload } = await post("decode", { hex: hexIn, type: "auto", enrich: false });
// edit payload …
const { hex } = await post("encode", { payload, type: "cpm" });
console.log(hex);   // round-trips if unchanged
Node.js — native fetch (18+)
const API = "https://v2json.skyv2x.com/api/v1";
const post = (p, body) => fetch(`${API}/${p}`, {
  method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body),
}).then((r) => r.json());

const { payload } = await post("decode", { hex: process.argv[2], type: "auto", enrich: false });
const { hex } = await post("encode", { payload, type: "cpm" });
console.log(hex);

Verify a round-trip

One call decodes a hex, re-encodes it, and reports whether the wire bytes match, so the codec checks itself.

curl
curl -s -X POST https://v2json.skyv2x.com/api/v1/verify \
  -H 'Content-Type: application/json' \
  -d '{"hex":"020E0001869F028251F6C74A962F0B0365833700320190000D6D83100300001241E008058010000C00603E80638032018E0000063E045897FFE25FFF890419E0"}'
# → { "round_trip": true, "message_type": "CPM", "original_bytes": 64, "reencoded_bytes": 64 }

Common questions

How do I decode an ETSI V2X message?

Send the hex UPER stream in a POST to /api/v1/decode. The response is structured JSON, with the message type auto-detected from the messageID. No SDK or local ASN.1 toolchain.

Can I encode JSON back to V2X UPER hex?

Yes. POST the decoded message to /api/v1/encode with its type. The same service runs both directions, and an unchanged message round-trips byte-for-byte.

Which V2X message types are supported?

The ETSI C-ITS message set: CAM, DENM, CPM, VAM, MAPEM, SPATEM, SREM, SSEM and IVIM.

Do I need an API key or a signup?

No. The API is free and open, rate-limited to 120 requests per minute per IP.

How do I check my own V2X encoder against a reference?

POST a hex to /api/v1/verify. It decodes and re-encodes the message and reports whether the wire bytes match, so you can validate an encoder against a byte-exact reference.

Other endpoints

GET /api/v1/types

The message types the running binary supports.

GET /api/v1/samples/{type}

A ground-truth sample hex per type, to try the API without your own capture.

GET /api/v1/health

Liveness and the binary version.

GET /docs

Interactive OpenAPI (Swagger UI): the full schema.

Open the workbench OpenAPI / Swagger ↗