SAP BNAC Fulfillment Service
Warning
SAP BNAC Fulfillment Service is currently in experimental state and should only be used for prototypes / experimentation.
Known limitations
- Automatic matching does not check the requesting partner. When two partners send requests with the same purchase order number and line, a shell prepared for one of them can be delivered to and shared with the other. If you accept requests from more than one partner, use manual processing, or make sure that your partners' purchase order data never overlaps. An improvement for this topic is coming soon.
- Documents do not reach SAP BNAC yet. A delivery creates the equipment and a document for each document of the Handover Documentation, but the files are not attached to these documents, and the documents are not linked to the equipment. The delivery still succeeds. This is a known bug at the moment and we are working on it.
- SAP BNAC import limits apply. SAP BNAC accepts packages smaller than 400 MB only.
twinsphere Cloud offers first-class integration with SAP Business Network Asset Collaboration (SAP BNAC), so that your customers receive the digital twins of the devices they ordered from you directly in SAP BNAC, built from the asset administration shells you already maintain in twinsphere.
In SAP BNAC, a customer who has ordered devices from you asks for their digital data by sending you an equipment request. The request lists one line item per device, identified by the purchase order and the line within it.
The SAP BNAC Fulfillment Service answers these requests from your twinsphere tenant. twinsphere picks up the equipment requests your customers send you, finds the shell for each line item, delivers it to SAP BNAC as equipment, shares that equipment with the customer and assigns it to the line item. Once every line item is answered, the request is handed back to your customer for review. You keep your device data in twinsphere, and you do not have to enter anything in SAP BNAC by hand.
The following terms are used throughout this page:
| Term | Meaning |
|---|---|
| Equipment request | A customer's request in SAP BNAC for the data of the devices it ordered from you |
| Line item | One requested device within an equipment request |
| Equipment | The SAP BNAC record of a device, created from the shell and submodels twinsphere delivers |
| Partner | A customer you accept equipment requests from, identified by its SAP BNAC business partner id |
| Authorization group | The SAP BNAC group equipment is shared into, so that only the partner of that group can see it |
| Fulfillment | twinsphere's record of one equipment request: its state, its line items and what was delivered for each |
Refer to the Sphere Server Swagger documentation (available on your tenant at /sphere/swagger/index.html) for
detailed information on each parameter and return value.
Prerequisites
- An SAP BNAC subscription with API access. SAP BNAC runs as an instance in a subaccount of your SAP Business Technology Platform (SAP BTP) account. Create a service key on that instance; it holds the connection details and credentials twinsphere needs (see Configuration).
- An authorization group for each partner. Equipment is shared into the partner's authorization group, so the group has to exist in SAP BNAC before anything can be delivered to that partner.
- SAP BNAC Fulfillment Service activated for your tenant. Contact us to have it activated.
- One of the following roles on the tenant:
| Role | Access |
|---|---|
tenant-bnac-fulfillment-viewer |
Read the configuration, the fulfillments and the jobs |
tenant-bnac-fulfillment-operator |
Everything the viewer can, plus changing the configuration and starting syncs, deliveries and finalizations |
tenant-administrator, tenant-global-writer and the organization owner have operator access as well;
tenant-global-reader has viewer access. See Roles for how roles are assigned.
Configuration
A tenant has exactly one BNAC configuration. It specifies how twinsphere reaches SAP BNAC, your identity in BNAC, how equipment requests are processed, and which partners you accept equipment requests from.
| Operation | Method | Endpoint | Description |
|---|---|---|---|
| Get configuration | GET |
/sphere/api/v1/bnac/configuration |
Returns the configuration, with the client secret masked |
| Set configuration | PUT |
/sphere/api/v1/bnac/configuration |
Creates or replaces the configuration, except the credentials |
| Set credentials | PUT |
/sphere/api/v1/bnac/configuration/credentials |
Sets or rotates the credentials |
Set the configuration
PUT https://{twinsphereTenantURL}/sphere/api/v1/bnac/configuration
Example Request
PUT /sphere/api/v1/bnac/configuration
Authorization: Bearer {token}
Content-Type: application/json
{
"bnacApiBaseUrl": "...",
"myBusinessPartnerId": "{your-business-partner-id}",
"processing": "automatic",
"autoFinalize": false,
"partners": [
{
"name": "Musterchemie GmbH",
"businessPartnerId": "{partner-business-partner-id}",
"authorizationGroupId": "{partner-authorization-group-id}"
}
]
}
| Field | Required | Description | Hint |
|---|---|---|---|
bnacApiBaseUrl |
yes | Base URL of the SAP BNAC API | In the SAP BTP cockpit, open your subaccount → Services → Instances and Subscriptions → Instances, and view the service key of your SAP BNAC instance (the link in the Credentials column). Take the value of endpoints.ain.url |
myBusinessPartnerId |
yes | Your own business partner id in SAP BNAC | In SAP BNAC, open the Company Profile app → External IDs and take the Object ID. It is a 32-character hexadecimal id |
processing |
no | automatic or manual, see Processing modes. Defaults to automatic |
|
autoFinalize |
no | Marks the equipment request as "done" from your side, as soon as every line item is answered. Must be false with manual processing. Defaults to false |
|
partners[].name |
yes | Your name for the partner, shown on its fulfillments | Free text, for example the company name the partner has in the SAP BNAC Business Partners |
partners[].businessPartnerId |
yes | The partner's business partner id in SAP BNAC. Equipment requests are attributed to a partner by it, so it must be unique across partners | The partner's own id from its Company Profile app, so ask the partner for it. Or configure the other fields first: a request from a partner that is not configured yet appears as a partner-not-configured fulfillment |
partners[].authorizationGroupId |
yes | The authorization group equipment for this partner is shared into | In SAP BNAC, open the Network Authorizations app → Groups and select the group; the id is part of the page URL |
Note
autoFinalize is off by default on purpose. Marking an equipment request as "done" cannot be undone
from your side; only the customer can send it back to you. Review a few requests before you switch it on.
An equipment request from a business partner that is not in partners is recorded, but nothing is
delivered for it. Add the partner to the configuration, and the request is processed with the next sync.
Set the credentials
PUT https://{twinsphereTenantURL}/sphere/api/v1/bnac/configuration/credentials
twinsphere signs in to SAP BNAC with the OAuth client credentials of a BNAC service key.
All values are in the service key of your SAP BNAC instance. In the SAP BTP cockpit, open your subaccount → Services → Instances and Subscriptions → Instances, and view the key through the link in the Credentials column. If the instance has no key yet, select it and create one under Service Keys.
Example Request
PUT /sphere/api/v1/bnac/configuration/credentials
Authorization: Bearer {token}
Content-Type: application/json
{
"authenticationBaseUrl": "https://...",
"clientId": "{client-id}",
"clientSecret": "{client-secret}"
}
| Field | Description | Hint |
|---|---|---|
authenticationBaseUrl |
Base URL of the OAuth server of your SAP BTP subaccount | uaa.url in the service key |
clientId |
OAuth client id | uaa.clientid in the service key |
clientSecret |
OAuth client secret | uaa.clientsecret in the service key |
The client secret is stored encrypted and is never returned.
How a request is processed
Once an hour, and whenever you start a sync yourself, twinsphere asks SAP BNAC for the equipment requests that are waiting for you. A request it sees for the first time is stored as a fulfillment. Every fulfillment that is not finished yet is then synced: twinsphere reads the request and its line items from SAP BNAC again and brings the fulfillment in line with them.
In automatic processing, the sync then takes the request as far as it can:
it finds a shell for each line item, delivers it, and, with autoFinalize, finalizes the request once every
line item is answered.
In manual processing, the sync stops after reading, and you trigger each of these steps yourself.
Delivering a line item takes the following steps in SAP BNAC:
- The shell and the submodels it references are uploaded to BNAC as equipment.
- The equipment is published and shared into the partner's authorization group.
- The equipment is assigned to the line item.
Finalizing a request (the Recommend action in the SAP BNAC UI) hands it back to your customer for review. Your customer then either confirms the result, or sends the request back to you. A request that comes back is picked up by the next sync and processed again like any other.
Equipment request status (BNAC side)
The requestStatusCode of a fulfillment is the status of the equipment request in SAP BNAC, as of
lastSyncedAt. Deliveries and finalizations change the status in SAP BNAC; the fulfillment shows the new
status after the next sync.
| BNAC Code | Status | What it means for you |
|---|---|---|
1 |
Draft | Your customer is still editing the request. It is not picked up yet. |
2 |
Author Action | The request is back with your customer. Nothing can be delivered until it is submitted again. |
3 |
Rejected | The request was rejected. The fulfillment is closed. |
4 |
Completed | The request was finalized and waits for your customer's review. |
5 |
In Process | The request is being answered. The first equipment twinsphere assigns moves a submitted request to in process. |
6 |
Submitted | Your customer has sent the request to you, and nothing has been answered yet. This is the state before "in process". |
7 |
Confirmed | Your customer has confirmed the result. The request has ended. |
9 |
Deleted | The request was deleted. The fulfillment is closed. |
Only requests in 5 In Process or 6 Submitted are processed. A request that returns to one of them, for
example because your customer sent it back, is processed again.
Fulfillment states (twinsphere side)
| State | Meaning |
|---|---|
open |
No line item carries equipment yet |
partially-fulfilled |
Some line items carry equipment |
fulfilled |
Every line item carries equipment |
partner-not-configured |
The request comes from a business partner that is not in your configuration. Nothing is delivered until you add the partner |
finalized |
The request was finalized and handed back to your customer |
closed |
The request ended without being finalized by twinsphere, for example because it was rejected, deleted, or completed in SAP BNAC directly |
The first three states follow from the line items and change whenever a line item does, including when
SAP BNAC reports line items that were added or removed. A finalized or closed fulfillment becomes
open, partially-fulfilled or fulfilled again when its request returns to In Process or Submitted.
The lastError of a fulfillment tells why the last sync or finalization of the request as a whole did not
succeed. It is absent once a later attempt succeeds.
Line item states
| State | Meaning |
|---|---|
unmatched |
No shell answers the line item yet |
matched |
A shell answers the line item, but it has not been delivered yet |
equipment-shared |
The equipment was created and shared, but not yet assigned to the line item |
equipment-assigned |
The equipment is assigned to the line item |
matchSource says how the shell was chosen:
| Value | Meaning |
|---|---|
automatic |
Found by its shell extensions, see Matching shells |
manual |
Pinned through the mapping endpoint |
external |
The line item already carried equipment in SAP BNAC that twinsphere did not deliver |
twinsphere never overwrites equipment it did not deliver. An external line item is shown as
equipment-assigned and cannot be mapped or delivered. It returns to unmatched once the equipment is
removed from the line item in SAP BNAC.
Every sync compares the line items with what SAP BNAC reports for them. The lastError of a line item tells
why its last delivery did not succeed, or which difference the sync found, for example:
- Your customer rejected the equipment. The line item stays
equipment-assignedand keeps the error. Correct the data and deliver the line item again, or pin another shell. - The equipment was removed from the line item in SAP BNAC. The line item returns to
matched. In automatic processing, the same sync delivers it again; in manual processing, deliver it yourself. - SAP BNAC reports equipment other than the one twinsphere assigned. The line item keeps the error, so that you can check the line item in SAP BNAC.
A line item that SAP BNAC no longer reports is removed from the fulfillment.
Processing modes
The processing field of the configuration decides whether twinsphere answers
equipment requests on its own, or only when you ask it to.
Automatic processing
With processing set to automatic, every sync matches, delivers and, with autoFinalize, finalizes on
its own. Mark your shells with the extensions below, and requests are answered without any further action.
Line items that cannot be answered stay open, and the sync tries again the next time. A line item whose
delivery failed carries the reason in its lastError and is delivered again by the next sync.
With autoFinalize set to true, a sync finalizes a request once it is fulfilled and no line item carries
an error. A request whose customer rejected one of the equipment is therefore not finalized again until
that line item is corrected.
Matching shells
A sync finds the shell for a line item by the extensions on the shell. Add them to the shell itself, not to one of its submodels:
| Extension name | Required | Value |
|---|---|---|
twinsphere.io/bnac/purchase-order |
yes | The purchase order number of the line item |
twinsphere.io/bnac/purchase-order-line-item |
yes | The line within the purchase order |
twinsphere.io/bnac/operator-equipment-id |
no | Your customer's own identifier for the device, when the line item names one |
{
"id": "https://example.com/shells/pump-4711",
"extensions": [
{ "name": "twinsphere.io/bnac/purchase-order", "valueType": "xs:string", "value": "4500012345" },
{ "name": "twinsphere.io/bnac/purchase-order-line-item", "valueType": "xs:string", "value": "00010" },
{ "name": "twinsphere.io/bnac/operator-equipment-id", "valueType": "xs:string", "value": "P-101" }
],
"assetInformation": { "assetKind": "Instance", "globalAssetId": "https://example.com/assets/pump-4711" }
}
A shell answers a line item:
- when both the purchase order and the line match exactly. They are compared as text, so
00010and10are different lines. Use the values exactly as your customer entered them in SAP BNAC. - and when it either names no operator equipment id, or the same one as the line item. A shell that names a different one is meant for another device and is never used for this line item.
A shell that names the line item's operator equipment id is preferred over one that names none. When several
shells are equally suitable, the one with more twinsphere.io/bnac/ extensions is used; the choice is
always the same for the same shells.
Matching only fills line items that have no shell yet. Changing the extensions of a shell later does not change a line item it already answers. To have such a line item matched again, release its shell, or pin another one.
Manual processing
With processing set to manual, the syncs still pick up new requests once an hour and keep every
fulfillment in line with SAP BNAC, but they never change anything in SAP BNAC. Shells are not matched by
their extensions. Each step is an operation you call yourself, so you see its outcome and its errors
directly. autoFinalize must be false in this mode.
A typical client does the following:
- Find open work:
GET /sphere/api/v1/bnac/fulfillments?state=open&state=partially-fulfilled. A fulfillment that has just been discovered has no line items yet; they appear with its first sync, shortly after. - Prepare the data: create the shell and its submodels, see Data requirements. The shell needs no BNAC extensions.
- Pin the shell to the line item, see Pin a shell.
- Deliver the line item, see Deliver a line item, and follow the job until it has
succeededorfailed. - Finalize the request, see Finalize a request, once every line item is answered.
Data requirements
SAP BNAC creates the equipment from the AAS package twinsphere delivers. The package contains the shell,
every submodel the shell references, and their concept descriptions. twinsphere sends it as it is; SAP BNAC
validates it, and a package it does not accept fails the delivery with SAP BNAC's reason in lastError.
SAP BNAC requires the following submodels:
- Nameplate V3.0 (SemanticId
https://admin-shell.io/idta/nameplate/3/0/Nameplate), exactly one per shell. SAP BNAC creates the equipment from it. - Handover Documentation V2.0 (SemanticId
0173-1#01-AHF578#003) for the documentation — recommended. - Handover Documentation V1.2 (SemanticId
0173-1#01-AHF578#001) is supported as well, but SAP BNAC accepts only oneDocumentin it. Prefer V2.0, which has no such limit.
SAP BNAC identifies equipment by the serial number of the device. Delivering a shell again therefore updates the existing equipment instead of creating a second one.
Endpoints
Operations marked async answer 202 Accepted right away and run as a job in the background; all
others answer directly with their result. The complete OpenAPI specification of these endpoints is available in
the Sphere Server Swagger documentation on your tenant, at /sphere/swagger/index.html.
| Operation | Method | Endpoint | Execution | Description |
|---|---|---|---|---|
| List fulfillments | GET |
/sphere/api/v1/bnac/fulfillments |
sync | Lists the fulfillments, newest first |
| Get fulfillment | GET |
/sphere/api/v1/bnac/fulfillments/{fulfillmentId} |
sync | Returns one fulfillment with its line items |
| List line items | GET |
/sphere/api/v1/bnac/fulfillments/{fulfillmentId}/items |
sync | Returns the line items of a fulfillment |
| Get line item | GET |
/sphere/api/v1/bnac/fulfillments/{fulfillmentId}/items/{itemId} |
sync | Returns one line item |
| Sync all | POST |
/sphere/api/v1/bnac/fulfillments/sync |
async (job) | Queues a sync of all equipment requests |
| Sync fulfillment | POST |
/sphere/api/v1/bnac/fulfillments/{fulfillmentId}/sync |
async (job) | Queues a sync of one fulfillment |
| Pin a shell | PUT |
/sphere/api/v1/bnac/fulfillments/{fulfillmentId}/items/{itemId}/mapping |
sync | Sets the shell that answers a line item |
| Release a shell | DELETE |
/sphere/api/v1/bnac/fulfillments/{fulfillmentId}/items/{itemId}/mapping |
sync | Removes the shell from a line item |
| Deliver a line item | POST |
/sphere/api/v1/bnac/fulfillments/{fulfillmentId}/items/{itemId}/deliver |
async (job) | Queues the delivery of one line item |
| Finalize a request | POST |
/sphere/api/v1/bnac/fulfillments/{fulfillmentId}/finalize |
async (job) | Queues the finalization of a request |
| List jobs | GET |
/sphere/api/v1/bnac/fulfillments/jobs |
sync | Lists the jobs, newest first |
| Get job | GET |
/sphere/api/v1/bnac/fulfillments/jobs/{jobId} |
sync | Returns one job |
List fulfillments
GET https://{twinsphereTenantURL}/sphere/api/v1/bnac/fulfillments
| Parameter | Description |
|---|---|
state |
Fulfillment state, see Fulfillment states |
partnerId |
Business partner id of the requester |
equipmentRequestId |
Id of the equipment request in SAP BNAC |
caseId |
Case number SAP BNAC shows for the equipment request |
limit |
Page size, at most 50. Defaults to 25 |
cursor |
Cursor from paging_metadata of the previous page, see Pagination |
Repeat a parameter to match any of its values, for example ?state=open&state=partially-fulfilled.
Different parameters are combined, so a fulfillment has to match all of them.
Example Response (200 OK)
{
"paging_metadata": {},
"result": [
{
"identifier": "{fulfillment-id}",
"equipmentRequestId": "{equipment-request-id}",
"caseId": "1000123",
"description": "Pumps for plant extension",
"requesterBusinessPartnerId": "{partner-business-partner-id}",
"partnerName": "Musterchemie GmbH",
"authorizationGroupId": "{partner-authorization-group-id}",
"requestStatusCode": "5",
"discoveredAt": "2026-09-28T09:15:02+00:00",
"lastSyncedAt": "2026-09-28T10:15:04+00:00",
"state": "partially-fulfilled",
"items": [
{
"identifier": "{item-id-1}",
"purchaseOrder": "4500012345",
"purchaseOrderLineItem": "00010",
"operatorEquipmentId": "P-101",
"state": "equipment-assigned",
"matchedShellId": "https://example.com/shells/pump-4711",
"matchSource": "automatic",
"assignedEquipmentId": "{equipment-id}"
},
{
"identifier": "{item-id-2}",
"purchaseOrder": "4500012345",
"purchaseOrderLineItem": "00020",
"state": "unmatched"
}
]
}
]
}
Fields without a value are left out of the response.
Syncs
POST /sphere/api/v1/bnac/fulfillments/sync does what the hourly sync does: it picks up requests that have
not been seen yet and queues a sync of every fulfillment that is not finished. The result.summary of the job
tells what it found:
| Field | Meaning |
|---|---|
equipmentRequestsListed |
Equipment requests SAP BNAC listed as waiting for you |
fulfillmentsDiscovered |
Requests seen for the first time and stored as fulfillments |
jobsQueued |
Fulfillment syncs queued |
alreadyQueued |
Fulfillment syncs that were already waiting from an earlier sync |
discoveryFailures |
One message per equipment request that could not be read from SAP BNAC |
POST /sphere/api/v1/bnac/fulfillments/{fulfillmentId}/sync syncs a single fulfillment, for example right
after you created the shells for it, instead of waiting for the next hourly sync.
Note
A sync job succeeds even when single line items could not be delivered. It fails only when the request
itself could not be processed. Check the lastError of the fulfillment and of its line items to see what
did not go through.
Pin a shell
PUT https://{twinsphereTenantURL}/sphere/api/v1/bnac/fulfillments/{fulfillmentId}/items/{itemId}/mapping
{
"shellId": "https://example.com/shells/pump-4711"
}
Sets the shell for the line item, in place of the one matching would choose.
In manual processing, this is the only way to give a line item a shell. The request answers directly with
204 No Content; the shell is delivered by the next delivery or, in automatic
processing, by the next sync.
A line item that was already delivered can be given another shell. It then returns to matched, and the new
shell is delivered in its place. Pinning is refused with 400 Bad Request when:
- the shell does not exist, or it already answers another line item of the same request,
- the line item carries equipment twinsphere did not deliver (
external), - the same shell was already delivered for the line item — deliver the line item again instead,
- the fulfillment is
finalizedorclosed.
Release a shell
DELETE https://{twinsphereTenantURL}/sphere/api/v1/bnac/fulfillments/{fulfillmentId}/items/{itemId}/mapping
Removes the shell from a line item that has not been delivered yet (matched). The line item returns to
unmatched. In automatic processing, the next sync matches it again; in manual processing, it stays
open until you pin a shell.
Deliver a line item
POST https://{twinsphereTenantURL}/sphere/api/v1/bnac/fulfillments/{fulfillmentId}/items/{itemId}/deliver
Queues the delivery of the line item's shell. Use it to deliver a pinned shell in manual processing, and to deliver a line item again after its shell or submodels changed, so that the equipment in SAP BNAC is updated.
Finalize a request
POST https://{twinsphereTenantURL}/sphere/api/v1/bnac/fulfillments/{fulfillmentId}/finalize
{
"allowIncomplete": false
}
Queues the finalization of the request, which hands it back to your customer for review. The body is optional.
By default, every line item has to carry equipment. Set allowIncomplete to true to finalize a request in
which some line items are still open; at least one line item has to carry equipment either way.
Important
A finalization cannot be undone from your side. Only your customer can send the request back to you.
Jobs
Syncs, deliveries and finalizations run as jobs in the background, because they depend on SAP BNAC. The
endpoints that start them answer 202 Accepted with the job in the body and its address in the Location
header. Get the job from there until its state is succeeded or failed.
Get a job
GET https://{twinsphereTenantURL}/sphere/api/v1/bnac/fulfillments/jobs/{jobId}
Example Response (200 OK)
{
"identifier": "{job-id}",
"kind": "deliver-item",
"trigger": "user-request",
"state": "succeeded",
"queuedAt": "2026-09-28T10:20:00+00:00",
"startedAt": "2026-09-28T10:20:01+00:00",
"completedAt": "2026-09-28T10:20:43+00:00",
"attempts": 1,
"parameters": {
"fulfillmentId": "{fulfillment-id}",
"itemId": "{item-id-1}"
},
"result": {
"delivery": {
"itemState": "equipment-assigned",
"assignedEquipmentId": "{equipment-id}"
}
}
}
| Field | Values |
|---|---|
kind |
tenant-sync (sync all), sync-fulfillment, deliver-item, finalize |
trigger |
user-request when started through the API, schedule for the hourly sync and the fulfillment syncs it queues |
state |
pending (waiting to start), running, succeeded, failed |
error |
Why the job failed. Only set when state is failed |
result |
summary for tenant-sync, delivery for deliver-item. Only set when state is succeeded |
GET /sphere/api/v1/bnac/fulfillments/jobs lists the jobs, newest first. Filter them by kind, state,
trigger and fulfillmentId, with the same rules and paging as the fulfillment list.
How jobs run
- One at a time per fulfillment. The jobs of one fulfillment run one after another, so a sync, a delivery and a finalization never change the same fulfillment at once. Jobs of different fulfillments run in parallel.
- No duplicates while waiting. A job that is the same as one still
pendingis refused with409 Conflict; the waiting job already covers it. Once that job has started, the same job can be queued again. - Failed jobs are not retried. Start the operation again to retry it. Running a sync or a delivery again is safe: finished work is not repeated, and a delivery updates the same equipment. A finalization that already went through is refused instead of being sent a second time.
- Time limit. A job that runs longer than one hour is stopped and fails.
- Restarts. A job that is interrupted by a twinsphere update returns to
pendingand runs again. Itsattemptscounts every start.