Quickstart: usage mediation
This walks a usage-based charge from a raw CDR (call/transaction detail
record) through to a rated transaction: submit a CDR, see it become an EDR,
rate it into a wallet operation, then convert that into a billable rated
transaction. Every step ran live against http://localhost:8080.
This quickstart targets an existing demo subscription
(CARREFOUR_BE_INV-GRP-00001, part of the Pluxee B2M Belgium demo data)
instead of building a new usage-charge catalog from scratch — usage charges
need an access point, filter-matched parameters, and a unit-of-measure setup
that already exist correctly wired in that demo data. No existing rows are
modified; every CDR submitted here is a new, additive record.
Step count: 7 steps, all run live in this session.
Prerequisites
A bearer token (see Authentication) and knowledge of which access code and charge-template filter parameters to target. Both were looked up live rather than assumed:
curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/api/v1/query/access?limit=10&fields=id,accessUserId,subscription.code"
returned, among others, accessUserId: "CARREFOUR_BE_INV-GRP-00001" on
subscription id 24. Checking that subscription's active charge instances:
curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/api/v1/query/chargeInstance?filters=%7B%22subscription.id%22%3A24%7D&fields=id,code,status"
showed CHG_MEAL_4C (USAGE, ACTIVE). Its filter formula, from
GET /api/v1/charges/CHG_MEAL_4C:
"filter_fx":"#{edr.parameter1 == 'MEAL' && edr.parameter2 == 'PHYSICAL' && edr.parameter3 == '4C'}",
"filterParam1":"MEAL","filterParam2":"PHYSICAL","filterParam3":"4C",
"pricingVersions":[{"pricingModel":"PER_UNIT","status":"PUBLISHED","price":0.040000000000}]
So a CDR with parameter1=MEAL, parameter2=PHYSICAL, parameter3=4C
against this access code rates at 0.04 EUR per unit under
TXC_BE_STANDARD (21% VAT, confirmed by the amounts below).
Step 1: register a CDR (no rating yet)
POST /api/v1/cdrs/registration persists the CDR as an EDR without rating
it — useful to decouple ingestion from rating, or to batch-validate before
committing to a rating pass.
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"cdrs":[
{
"eventDate":"2026-07-09T13:00:00Z",
"quantity": 3,
"accessCode":"CARREFOUR_BE_INV-GRP-00001",
"parameter1":"MEAL",
"parameter2":"PHYSICAL",
"parameter3":"4C"
}
]
}' \
http://localhost:8080/api/v1/cdrs/registration
Response (200):
{"mode":"STOP_ON_FIRST_FAIL","statistics":{"total":1,"success":1,"fail":0},"amountWithoutTax":0,"amountTax":0,"amountWithTax":0,"walletOperationCount":0,"items":[{"index":0,"status":"REGISTERED","edrId":190002}],"limits":{"syncMax":1000,"asyncMax":100000}}
walletOperationCount: 0 and all amounts 0 because registration does not
rate. edrId: 190002 is the new EDR.
Step 2: check the EDR
curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/api/v1/query/edr/190002?fields=id,status,quantity,parameter1,parameter2,parameter3,eventDate,subscription"
Response:
{"data":{"id":190002,"subscription":{"id":24},"eventDate":1783602000000,"quantity":3.000000000000,"parameter1":"MEAL","parameter2":"PHYSICAL","parameter3":"4C","status":"OPEN"}}
status: OPEN — registered, not yet rated.
Step 3: submit and rate a CDR in one call
POST /api/v1/cdrs/rating registers and rates in the same call. The
returnEDRs/returnWalletOperations/returnWalletOperationDetails query
flags control how much of the generated data comes back inline (all
default false to keep the response small on high-volume bulk loads).
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
"http://localhost:8080/api/v1/cdrs/rating?returnEDRs=true&returnWalletOperations=true&returnWalletOperationDetails=true" \
-d '{
"cdrs":[
{
"eventDate":"2026-07-09T13:05:00Z",
"quantity": 5,
"accessCode":"CARREFOUR_BE_INV-GRP-00001",
"parameter1":"MEAL",
"parameter2":"PHYSICAL",
"parameter3":"4C"
}
]
}'
Response (200):
{"mode":"STOP_ON_FIRST_FAIL","statistics":{"total":1,"success":1,"fail":0},"amountWithoutTax":0.20,"amountTax":0.04,"amountWithTax":0.24,"walletOperationCount":1,"items":[{"index":0,"status":"RATED","amountWithoutTax":0.20,"amountTax":0.04,"amountWithTax":0.24}]}
5 units x 0.04 EUR = 0.20 without tax, 0.04 tax (21%), 0.24 with tax —
exactly the charge's PER_UNIT price and tax class, live-computed by the
rating engine.
Step 4: confirm the wallet operation
curl -G -H "Authorization: Bearer $TOKEN" \
--data-urlencode 'filters={"subscription.id":24,"code":"CHG_MEAL_4C"}' \
--data-urlencode 'sortBy=id' --data-urlencode 'sortOrder=DESC' --data-urlencode 'limit=1' \
--data-urlencode 'fields=id,code,status,quantity,amountWithoutTax,amountTax,amountWithTax' \
http://localhost:8080/api/v1/query/walletOperation
Response:
{"total":1,"limit":1,"offset":0,"data":[{"id":190003,"code":"CHG_MEAL_4C","quantity":5.000000000000,"amountWithoutTax":0.200000000000,"amountWithTax":0.240000000000,"amountTax":0.040000000000,"status":"OPEN"}]}
Same amounts as the rating response. status: OPEN: exists, rated, but not
yet converted into a RatedTransaction — same two-step pipeline as
one-shot/recurring charges (see the
catalog-to-first-invoice quickstart).
Step 5: convert to a rated transaction (RT_Job)
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{}' \
http://localhost:8080/api/v1/jobInstances/RT_Job/executions
Response (202): {"executionId":1952,"jobInstanceCode":"RT_Job","status":"RUNNING"}
After a few seconds:
curl -H "Authorization: Bearer $TOKEN" "http://localhost:8080/api/v1/query/walletOperation/190003?fields=id,status,ratedTransaction"
{"data":{"id":190003,"ratedTransaction":{"id":145043},"status":"TREATED"}}
Step 6: read the rated transaction via the Query API
curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/api/v1/query/ratedTransaction/145043?fields=id,code,status,quantity,amountWithoutTax,amountTax,amountWithTax"
Response:
{"data":{"id":145043,"code":"CHG_MEAL_4C","quantity":5.000000000000,"amountWithoutTax":0.200000000000,"amountWithTax":0.240000000000,"amountTax":0.040000000000,"status":"OPEN"}}
Amounts identical end to end: CDR quantity 5 -> EDR quantity 5 -> WO amount
0.20/0.24 -> RT amount 0.20/0.24. status: OPEN on the RT means it is
billable but not yet on an invoice — the next step would be a bill run,
exactly as in the catalog-to-first-invoice
quickstart.
Step 7: the CSV dialect
/registration, /rating, and /reservation all accept text/plain
(single CDR line) and text/csv (multiple lines) in addition to JSON. The
CSV field order is fixed: eventDate;quantity;accessCode;parameter1..9; dateParam1..5;decimalParam1..5;extraParameter (23 fields,
;-delimited, empty fields left blank), matching
org.meveo.admin.parse.csv.MEVEOCdrParser. The date format for the CSV
dialect is yyyy-MM-dd'T'HH:mm:ssxx (a 4-digit zone offset with no colon,
e.g. +0200) — this resolves the TODO-VERIFY in API
principles about whether this format is CSV-only:
it is. JSON CDR items use plain ISO 8601 with a colon in the offset (or
Z), as shown in the earlier steps.
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: text/plain" \
--data-raw '2026-07-09T13:10:00+0200;2;CARREFOUR_BE_INV-GRP-00001;MEAL;PHYSICAL;4C;;;;;;;;;;;;;;;;;' \
http://localhost:8080/api/v1/cdrs/registration
Response (200):
{"mode":"STOP_ON_FIRST_FAIL","statistics":{"total":1,"success":1,"fail":0},"amountWithoutTax":0,"amountTax":0,"amountWithTax":0,"walletOperationCount":0,"items":[{"index":0,"status":"REGISTERED","edrId":190004}],"limits":{"syncMax":1000,"asyncMax":100000}}
Same registration behavior as the JSON dialect, confirming the two wire formats are equivalent.
What this proved, end to end
| Concept | Verified |
|---|---|
/registration vs /rating | registration persists an EDR only; rating also produces a WO in the same call |
| Filter-matched charge selection | parameter1/2/3 on the CDR must match the charge template's filterParam1/2/3 exactly |
| PER_UNIT pricing | quantity x price computed live, matches the charge's published price |
| WO -> RT conversion | same RT_Job mechanism as one-shot/recurring charges |
| JSON vs CSV dialect | equivalent; CSV uses a distinct date format (yyyy-MM-dd'T'HH:mm:ssxx), JSON uses standard ISO 8601 |