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
Labels in this area