Integration Blog Posts
cancel
Showing results for 
Search instead for 
Did you mean: 

If you operate integration scenarios on SAP Cloud Integration capability of SAP Integration Suite and rely on JMS messaging, you have likely needed to inspect messages, move messages to a different queue, or trigger a retry — all without opening the monitoring UI. The new JMS OData API makes all of that possible.

This post walks through the new public OData API MessagingQueues / MessagingMessages : what it covers, how to use it, and the details you need to get it right.

This new JMS OData API is available starting with SAP Cloud Integration version 7.52.x for the Cloud Foundry runtime and version 8.41 for the Edge Integration Cell runtime.

What the API Enables

The API exposes two entities: MessagingQueues and MessagingMessages. Together, they give you control over the JMS layer of your tenant.

Queue operations let you list all queues with their capacity metrics, inspect a single queue, and delete a queue when it is no longer needed.

Message operations let you list messages with filters, retrieve a single message by its composite key, download the raw payload bytes, delete individual messages, move messages between queues, and retry failed messages.

Getting Started

You need a Process Integration Runtime service instance with the api plan and the OAuth credentials from its service binding. All endpoints live under /api/v1/ on your tenant URL, and authentication uses OAuth 2.0 Client Credentials. The base URL for all requests:

https://{tenant-url}/api/v1/

A minimal workflow for programmatic error handling:

  1. GET /MessagingQueues to find all queues.
  2. GET /MessagingQueues('{queueName}')/MessagingMessages with filters scoped to your scenario (sender, correlationId, time range, etc.).
  3. Inspect the payload via /$value on interesting messages.
  4. POST /RetryMessagingMessages to retry failed messages, or POST /MoveMessagingMessages to redirect messages to a different queue.

The next sections cover each of these endpoints in detail.

API Endpoints

Listing Queues

GET https://{tenant-url}/api/v1/MessagingQueues

Each queue in the response looks like this:

{
  "d": {
    "results": [
      {
        "queueName": "orders-inbound",
        "numberOfMessages": 123,
        "active": true,
        "exclusive": false,
        "MessagingMessages": {
          "__deferred": {
            "uri": "https://{tenant-url}/api/v1/MessagingQueues('orders-inbound')/MessagingMessages"
          }
        }
      }
    ]
  }
}

To get the total number of queues without fetching the full list:

GET https://{tenant-url}/api/v1/MessagingQueues/$count

This returns a plain-text integer.

The queue feed itself has no row cap — all queues configured for the tenant are returned in a single response.

Listing and Filtering Messages

GET https://{tenant-url}/api/v1/MessagingQueues('orders-inbound')/MessagingMessages
    ?sender=OrderSystem
    &receiver=SAP-ERP
    &createdAfter=1771900000000
    &createdBefore=1771986400000

The API supports the following filter parameters — all optional, all AND-combined:

ParameterDescription
jmsMessageIdFilter by JMS message ID
correlationIdFilter by correlation ID
senderFilter by sender
receiverFilter by receiver
mplIdFilter by Message Processing Log ID
messageTypeFilter by message type
applicationIdFilter by application ID
createdAfter / createdBeforeTime bounds on SAP_CreatedTime (epoch ms UTC)
pageSizeAmount of entries to return

There is no $filter, $orderby, $select, $expand, or $search — these return 400 if supplied.

$top, $skip, $inlinecount, and $skiptoken are silently ignored (the server does not page on them; row count is governed solely by pageSize). $format is accepted. Unknown $* options return 400.

How Message Listing Works (no pagination)

The API does not support $top or $skip — both are silently ignored. Instead, the server applies a fixed row cap of 100 rows. You can override the default row cap per request with pageSize in the range 100–10,000.

The reason: message listing uses JMS browse semantics, not a random-access table. Queues change continuously — messages are consumed, expired, and reordered between requests. Offset paging ($skip=500) would scan the broker on every call, resulting in unstable results.

The supported approach for large datasets is time-window slicing:

# Window 1: Monday morning
GET …/MessagingMessages?sender=ACME&createdAfter=1777000000000&createdBefore=1777043200000

# Window 2: Monday afternoon
GET …/MessagingMessages?sender=ACME&createdAfter=1777043200000&createdBefore=1777086400000

If a window still hits the row cap, narrow it further by using a shorter time range or additional filters.

Getting a Single Message and its Payload

The composite key (jmsMessageId, queueName) can be used to get a single message:

GET https://{tenant-url}/api/v1/MessagingMessages(
  jmsMessageId='x-hex-3333...43864',
  queueName='orders-inbound'
)

To download the raw payload bytes of a message, append /$value:

GET https://{tenant-url}/api/v1/MessagingMessages(
  jmsMessageId='x-hex-3333...43864',
  queueName='orders-inbound'
)/$value

The server streams the payload directly from the broker and decompresses transparently if the message was stored compressed.

An empty payload returns 200 with Content-Length: 0.

A top-level GET /MessagingMessages (without a queue context) returns 404. If too many concurrent payload downloads are active on the instance, the server returns a 503 (SERVICE_UNAVAILABLE) error.

Deleting a Message

DELETE https://{tenant-url}/api/v1/MessagingMessages(
  jmsMessageId='x-hex-3333...43864',
  queueName='orders-inbound'
)

Response: 200 OK with a confirmation body:

{
 "operation": "DELETE",
 "processedCount": 1 
}

This is permanent and irreversible.

Deleting a Queue

DELETE https://{tenant-url}/api/v1/MessagingQueues('orders-inbound')

By default, the queue must be empty.

Pass forceDelete=true to delete and discard any remaining messages in the queue:

DELETE https://{tenant-url}/api/v1/MessagingQueues('orders-inbound')?forceDelete=true

Response on success: 200 OK with { "operation": "DELETE", "processedCount": 1 }.

If the queue still has messages and forceDelete is not true, the server returns 409 (QUEUE_NOT_EMPTY). If deployed integration flows still reference the queue, the server returns a 400 (QUEUE_IN_USE) error.

Moving Messages Between Queues

Move operations use POST /MoveMessagingMessages with a JSON body. Selection is mutually exclusive: whole queue, specific message IDs, or filter-based.

Move Entire Queue

POST https://{tenant-url}/api/v1/MoveMessagingMessages
Header: 
- Content-Type: application/json
Body:
{
  "sourceQueue": "orders-error",
  "targetQueue": "orders-inbound"
}

Move Specific Messages

POST https://{tenant-url}/api/v1/MoveMessagingMessages
Header:
- Content-Type: application/json
Body:
{
  "sourceQueue": "orders-error",
  "targetQueue": "orders-inbound",
  "jmsMessageIds": ["x-hex-3333...43864", "x-hex-4444...43865"]
}

Move by Filter

POST https://{tenant-url}/api/v1/MoveMessagingMessages
Header:
- Content-Type: application/json
Body:
{
  "sourceQueue": "orders-error",
  "targetQueue": "orders-inbound",
  "correlationId": "abc123",
  "createdAfter": "1771900000000",
  "createdBefore": "1771986400000"
}

Response: 200 OK with { "operation": "MOVE", "processedCount": 42 }. For ID- or filter-based selection, processedCount is the number of messages actually moved. For a whole queue move, it reflects the queue's numberOfMessages at dispatch time, not a recount after the operation.

Unknown body fields return a 400 (INVALID_INPUT). The source and target queue must differ. Missing source or target queue returns a 404 (NOT_FOUND) error.

Retrying Failed Messages

Retry operations use POST /RetryMessagingMessages. The same selection options apply: whole queue, single message, message IDs, or filters.

Retry Entire Queue

POST https://{tenant-url}/api/v1/RetryMessagingMessages
Header:
- Content-Type: application/json
Body:
{ 
  "queueName": "orders-inbound"
}

Retry a Single Message

POST https://{tenant-url}/api/v1/RetryMessagingMessages
Header:
- Content-Type: application/json
Body:
{
  "queueName": "orders-inbound",
  "jmsMessageId": "x-hex-3333...43864"
}

Response: 200 OK with { "operation": "RETRY", "processedCount": 1 }. The same reporting rules from move operations apply: actual count for ID/filter selection, numberOfMessages at dispatch time for whole-queue retry.

Retrying on an stopped queue returns 400 (INVALID_INPUT). If the message does not exist, it returns a 404 (NOT_FOUND) response.

Technical Details Every Consumer Should Know

The Composite Key

Every MessagingMessage is identified by two components: jmsMessageId (the JMS Message ID) and queueName (the queue it lives in).

Timestamp Convention

All timestamp values in response properties (createdAt, overdueAt, expirationDate, nextRetry) and in filter parameters (createdAfter, createdBefore) — are epoch milliseconds UTC, represented as a 13-digit integer. For example, 1771933250829 is 2026-03-25T06:40:50.829Z.

OAuth 2.0 Scopes

The API uses OAuth 2.0 Client Credentials. Obtain a token from your tenant's Process Integration Runtime service instance.

Role (NEO)Role-Templates (Cloud FoundryOperation
ESBDataStore.readDataStoresAndQueuesReadAll GET operations (queues, messages, artifacts)
ESBDataStore.readPayloadDataStorePayloadsReadPayload download via GET /$value
ESBDataStore.deleteDataStoresAndQueuesDeleteDELETE queue, DELETE message
ESBDataStore.retryQueuesRetryPOST /MoveMessagingMessages
POST /RetryMessagingMessages
ESBDataStore.ActivateQueuesActivateActivate / deactivate queue function imports

Audit Logging

Successful deletes, moves, retries, and payload downloads are recorded in the tenant audit log. Each entry captures who triggered the call, what was done, and the main details such as — queue names, target queue on move, whether forceDelete was used, and the processedCount on move and retry. Ordinary reads of queue or message metadata are not audit-logged. Only payload download (/$value) is treated as sensitive read access.

Error Responses

All API-layer errors use a consistent OData envelope:

{
  "error": {
    "code": "QUEUE_NOT_EMPTY",
    "message": {
      "lang": "en",
      "value": "Queue 'orders-inbound' contains messages. Pass forceDelete=true to delete anyway."
    }
  }
}

Error codes you will encounter on this surface: BAD_REQUEST, INVALID_FILTER, INVALID_INPUT, NOT_FOUND, QUEUE_IN_USE, QUEUE_NOT_EMPTY, QUEUE_NOT_DELETABLE, SERVICE_UNAVAILABLE, INTERNAL_ERROR.

Note that 401, 403, and 429 responses come from the surrounding runtime platform. Their shapes may not conform to the OData envelope above.

Share your Experience

Have you built operational tooling on top of the JMS OData API? We'd like to hear what patterns you've found useful.

11 Comments
Santhosh_Vellingiri
Active Contributor

Hi @MarcoKoch

Thank you for the detailed blog.

It would be very helpful if the overview /api/v1/MessagingQueues endpoint could provide a breakdown of message counts by status (for example, Waiting, Overdue, Failed, etc.) in addition to the existing numberOfMessages count.

Having status-level metrics available directly from the overview endpoint would enable operational monitoring tools to assess queue health more effectively and trigger alerts, workflows, or corrective actions when predefined thresholds are exceeded.

MarcoKoch
Product and Topic Expert
Product and Topic Expert

Hi @Santhosh_Vellingiri 

Thank you so much for your feedback! I really appreciate it and will make sure to discuss it with the team.

 

upputholla
Explorer

Really appreciate the detailed breakdown of the JMS OData API. The move and retry operations stand out as a big step toward operational automation.

In real projects, we often had to rely on manual monitoring UI or custom scripts for reprocessing failed messages. Having filter-based move and retry APIs now opens up possibilities for building automated recovery pipelines (especially for correlationId-based reprocessing).

One thing I’m curious about — have you seen customers implementing time-window slicing combined with scheduled jobs to handle high-volume queues reliably?

gscov
Newcomer

Hi @MarcoKoch , why is this not documented at https://api.sap.com/package/CloudIntegrationAPI/odata, especially regarding the new API policy?

RubikWal
Explorer

@upputholla 
Can you please elaborate on this : "customers implementing time-window slicing combined with scheduled jobs to handle high-volume queues reliably".   What does the issue you are facing, and what does the time-window slicing mean here.
Thank you.

 

upputholla
Explorer

@RubikWal 

In some cases (like when a downstream system was down), a large number of messages accumulated in the JMS queue.
When trying to reprocess everything at once, we observed:

  • API timeouts during retry
  • Performance degradation in CPI and target system
  • Difficulty isolating which messages were failing
  • Risk of overwhelming downstream systems

nstead of retrying all messages in one go, we split the backlog based on message creation timestamp (or enqueue time) into smaller chunks.

For example:

  • Retry messages from 10:00–10:10
  • Then 10:10–10:20
  • Then 10:20–10:30

So we process manageable batches instead of full queue at once.

 

  • Prevents system overload
  • Improves retry success rate
  • Makes troubleshooting easier (we know which time range had issues)
  • Gives better control over high-volume recovery
venkatamandavilli
Contributor

@MarcoKoch 

This is a practical developer-level post because it shows how to control JMS queues directly through the new OData API instead of manually provisioning. I like that it covers queue lifecycle, limits, and monitoring, since those are the things that usually cause problems when integration traffic grows. It feels especially useful for teams that rely heavily on JMS messaging and need programmatic control over their Cloud Integration tenant.

 

MarcoKoch
Product and Topic Expert
Product and Topic Expert

Hi @gscov 


why is this not documented at https://api.sap.com/package/CloudIntegrationAPI/odata, especially regarding the new API policy?

We are experiencing delays in releasing the documentation for the SAP Business Accelerator Hub update.
The API documentation will be released soon.

pelyvap_sbb
Explorer

Hi @MarcoKoch,

Thanks for the blog, it's a useful description of a long-awaited functionality. However:


You need a Process Integration Runtime service instance with the integration-flow plan and the OAuth credentials from its service binding. All endpoints live under /api/v1/ on your tenant URL, and authentication uses OAuth 2.0 Client Credentials.

Are you sure this isn't an "api" plan instead of an "integration-flow" one? My calls are successful using the API plan, but fail with the error "Requested route ('<tenant-specific-details>.cfapps.eu10.hana.ondemand.com') does not exist." if I go for the integration-flow plan.

This thread has helped me during troubleshooting:
https://community.sap.com/t5/technology-q-a/unable-to-access-cloud-integration-tenant-apis-like-mess...

MarcoKoch
Product and Topic Expert
Product and Topic Expert

Hi @pelyvap_sbb 

Thank you for your feedback. 
"api" plan is correct, and I have updated the blog post.

MarcoKoch
Product and Topic Expert
Product and Topic Expert

The documentation of these new APIs are available in SAP Business Accelerator Hub:

https://api.sap.com/api/MessageStore/resource/JMS_Resources

 

Labels in this area