logo

Are you need IT Support Engineer? Free Consultant

Migrate to the Usage Records OData API (v2) — Richer Queries, Simpler Integration

  • By Sanjay
  • 12/11/2026
  • 18 Views



We'd like to share an important change to the Usage Records API: As of October 27, 2026, the new OData API (v2) is available. The existing RESTful API (v1) was officially deprecated on November 9, 2026 and will be decommissioned on November 9, 2027. 

This blog explains the advantages of v2 and provides detailed instructions on migrating from v1 to v2.

Why the change? The v1 API used a proprietary query syntax that limited what you could do with your usage data. The v2 API is built on OData V4, an open standard published by OASIS and adopted across the SAP product portfolio. By aligning with OData V4, the Usage Records API becomes a first-class citizen in the SAP ecosystem:

  • SAP tooling works out of the box — SAP BTP SDKs, CAP, SAP Fiori elements, and SAP Analytics Cloud all speak OData natively; no custom integration layer needed
  • Code generation — OData-aware generators can produce typed client code directly from the /$metadata endpoint
  • Broad ecosystem support — any OData V4-compatible client library, IDE extension, or BI tool (Power BI, Excel Power Query, and many open-source libraries) connects without custom adapters
  • Consistency across SAP — customers already familiar with OData from SAP S/4HANA, SAP SuccessFactors, or SAP Ariba can apply the same patterns and tooling here immediately

 

What's New in v2

Richer, More Flexible Data Retrieval

The retrieve operation is the most significant upgrade in v2. The v2 OData API introduces standard query options that give you precise control over what data is returned, and how much of it.

$select — Return Only the Fields You Need

v1 always returns all fields. With $select, you choose exactly which fields come back, reducing payload size and improving performance.

GET /UsageRecords
?$select=ID,metricId,technicalResourceId,startedAt,endedAt,quantity,assigned

$expand — Navigate Related Data in a Single Request

v1 returns items embedded by default with no control over depth. v2 gives you full control:

# Records with items and their fields
GET /UsageRecords?$expand=items($expand=fields)

# Combine $expand with $select at every level
GET /UsageRecords
?$select=ID,metricId,quantity
&$expand=items(
$select=itemNumber;
$expand=fields($select=fieldCode,stringValue)
)

Lambda Operators — Filter by Related Entity Values

This was not possible in v1. OData lambda operator any lets you filter records based on the content of their related collection navigation properties.

# Records with a specific custom reference type
GET /UsageRecords
?$filter=customReferences/any(c:c/typeCode eq ‘SYSTEM_TYPE')

Standard $filter with New Functions

The proprietary filter= parameter is replaced by standard OData $filter=. All operators from v1 are supported, plus string functions:

$filter=contains(metricId,'CALL')
$filter=startswith(technicalResourceId,'device-‘)
$filter=metricId in (‘EMAIL_SENT','EMAIL_RECEIVED')

The total count moves from the x-count response header into the response body:

GET /UsageRecords?$filter=category eq ‘MEASURED'&$count=true&$top=20{
“@context”: “$metadata#UsageRecords”,
“@count”: 1432,
“value”: [ … ]
}

Batch Operations

v1 $batch used a proprietary JSON format and only supported create/upsert. v2 batch follows the standard OData V4 multipart/mixed format, supporting upsert, discard, delete, and retrieve operations in a single request.

 

Migration Guide

1. Update the Base URL

Change /v1 to /v2 in the base URL. OAuth scopes are unchanged.

 

2. Rename Changed Fields

v1 Fieldv2 Field
userTechnicalIdtechnicalResourceId
isAssignedassigned
changedAtmodifiedAt
changedBymodifiedBy
subscriptionNumbersubscriptionDocumentNumber

All other field names are unchanged.

 

3. Migrate Write Operations

Single Record Create / Upsert

v1
POST /usage-records
Content-Type: application/json

{
“category”: “MEASURED”,
“metricId”: “API_CALL”,
“userTechnicalId”: “ce6ad4d2-…”,
“startedAt”: “2026-09-01T00:00:00Z”,
“endedAt”: “2026-10-01T00:00:00Z”,
“quantity”: 1200
}

v2
POST /UsageRecords/upsertByLogicalKey
Content-Type: application/json

{
“value”: {
“category”: “MEASURED”,
“metricId”: “API_CALL”,
“technicalResourceId”: “ce6ad4d2-…”,
“startedAt”: “2026-09-01T00:00:00Z”,
“endedAt”: “2026-10-01T00:00:00Z”,
“quantity”: 1200
}
}

Batch Operations

v2 $batch — Batch upsert example

Note: A changeset groups write operations atomically — either all succeed or all are rolled back. It is optional; write requests can also be sent individually in a batch without a changeset.

POST /$batch
Content-Type: multipart/mixed; boundary=batch_id-1778814615130-767

–batch_id-1778814615130-767
Content-Type: multipart/mixed; boundary=changeset_1

–changeset_1
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1-1

POST UsageRecords/upsertByLogicalKey HTTP/1.1
Content-Type: application/json

{
“value”: {
“category”: “MEASURED”,
“metricId”: “API_CALL”,
“technicalResourceId”: “…”,
“startedAt”: “2026-09-01T00:00:00Z”,
“endedAt”: “2026-10-01T00:00:00Z”,
“quantity”: 400
}
}

–changeset_1
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1-2

POST UsageRecords/upsertByLogicalKey HTTP/1.1
Content-Type: application/json

{
“value”: {
“category”: “MEASURED”,
“metricId”: “E_CHARGE”,
“technicalResourceId”: “…”,
“startedAt”: “2026-09-01T00:00:00Z”,
“endedAt”: “2026-10-01T00:00:00Z”,
“quantity”: 1
}
}

–changeset_1–

–batch_id-1778814615130-767–

 Discard / Delete
# Discard
v1: POST /usage-records/{id}/discard
v2: POST /UsageRecords({id})/discard

# Delete
v1: DELETE /usage-records/{id}
v2: DELETE /UsageRecords({id})

 

4. Migrate Read Operations

List Usage Records

v1
GET /usage-records
?filter=metricId eq ‘API_CALL'
and startedAt ge 2026-09-01T00:00:00Z
&sort=startedAt desc
&pageSize=50&pageNumber=1
v2
GET /UsageRecords
?$filter=metricId eq ‘API_CALL'
and startedAt ge 2026-09-01T00:00:00Z
&$orderby=startedAt desc
&$top=50

Pagination

The server automatically paginates results and returns @nextLink whenever more records exist. Always follow @nextLink to retrieve all pages.

Case 1 — No $top specified (default page size: 16)
GET /UsageRecords

# Response — up to 16 items, nextLink present if more exist
{
“value”: [ …up to 16 items… ],
“@nextLink”: “UsageRecords?$skiptoken=…”
}

# Follow nextLink to get the next page
GET /UsageRecords?$skiptoken=…

 Case 2 — $top exceeds 100 (silently capped)

 If $top is greater than 100, the server caps the result at 100 and returns @nextLink for the remainder — no error is returned.

GET /UsageRecords?$top=500

# Response — only 100 items returned, rest via nextLink
{
“value”: [ …100 items… ],
“@nextLink”: “UsageRecords?$top=500&$skiptoken=…”
}

Filter on Custom References

v1 — Flat dot notation only
?filter=customReferences.typeCode eq ‘SYSTEM_TYPE'
 v2 — Lambda operator
?$filter=customReferences/any(c:c/typeCode eq ‘SYSTEM_TYPE')

 

5. Update Response Handling

v2 wraps collection responses in a standard OData envelope. Extract records from the value array; read count from @countinstead of the x-count header.

v1 — Plain array
[{
“id”: “992e46d8-…”,
“isAssigned”: true,
“changedAt”: “2026-09-15T12:00:00Z”
}]
v2 — OData envelope
{
“@context”: “$metadata#UsageRecords”,
“@count”: 1,
“value”: [{
“ID”: “992e46d8-…”,
“assigned”: true,
“modifiedAt”: “2026-09-15T12:00:00Z”
}]
}

 

Migration Checklist

  • Change /v1 to /v2 in the base URL
  • Rename userTechnicalId → technicalResourceId, isAssigned → assigned, changedAt → modifiedAt, changedBy → modifiedBy, subscriptionNumber → subscriptionDocumentNumber
  • Change single-record write: POST /usage-records → POST /UsageRecords/upsertByLogicalKey with { “value”: { … } }
  • Change batch format: JSON → multipart/mixed
  • Change filter= → $filter=, sort= → $orderby=
  • Replace pageNumber/pageSize with @nextLink (recommended) or $top/$skip
  • Update count: x-count header → @count in response body
  • Update response parsing: root array → .value array
  • Update discard/delete URLs: /{id}/discard → ({id})/discard, /{id} → ({id})
  • (Recommended) Add $select to reduce payload on read-heavy integrations
  • (Recommended) Replace multi-request patterns with $expand

 Resources 





Source link

Leave a Reply

Your email address will not be published. Required fields are marked *

Chat with us on WhatsApp!