Skip to main content

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

ConceptVerified
/registration vs /ratingregistration persists an EDR only; rating also produces a WO in the same call
Filter-matched charge selectionparameter1/2/3 on the CDR must match the charge template's filterParam1/2/3 exactly
PER_UNIT pricingquantity x price computed live, matches the charge's published price
WO -> RT conversionsame RT_Job mechanism as one-shot/recurring charges
JSON vs CSV dialectequivalent; CSV uses a distinct date format (yyyy-MM-dd'T'HH:mm:ssxx), JSON uses standard ISO 8601