-
Notifications
You must be signed in to change notification settings - Fork 2
Home
The purpose of OMSA is to enable simple integrations between different public transport booking and reservation systems. OMSA was designed to enable actors to utilize open data from the National Access Points, to create value for themselves and for the travellers. OMSA aspires to be a tool for actors that want to collaborate on booking and reservation, and thereby lower the costs integration across the sector. OMSA is based on the premise of a distributed eco-system, where existing services, solutions, components and standards are re-used and re-purposed to create value for the players in the industry. Under the premise of "no need to re-invent the wheel", OMSA aims to add the value of collaboration and integration without the need for sizeable up-front investments.
- Financial: reduced operational costs, easy to implement, early return on investment
- Ease of use: global known meta standards, simple API, highly structured
- Steady, future-proof, modern technologies, compliance with many standards
- Enable true multimodal travel, which is the goal to strive for
- Return On Investment - structured & understandable
- Reuse (e.g. NeTEx, OTP outcomes)
In this use case, we describe how a traveller searches for travel options at the operator 4W, adds some personal details (because 4W does not allow anonymous travel) and purchases (books) the selected travel option. Btw, 4W is a bus operator, but it could have been any operator.
The traveller uses a reseller, who uses a trip planner. The trip planner requests travel options at 4W, one of the legs of the trip can be executed by 4W.
Reseller calls: https://4W.mobility/OMSA/v1/processes/search-offers/execute
{ "inputs": {
"type": "SEARCH_OFFER",
"timestamp": "2025-05-14T12:55:17.170Z",
"specification": {
"from": { "placeId": "JL:StopPoint:349-34123" }
"to": { "placeId": "GPS:4.43243,51.3492340" },
"startTime": "2025-05-14T12:55:17.170Z"
},
"travellers": [
{ "type": "user_profile",
"requirements": [ { "type": "service", "class": "FIRST_CLASS" } ],
"id": "9f7f69ec-d68c-4751-8ec4-fd059f820cb8",
"ageGroup": "ADULT" }
]
}
}Some key elements to highlight in this request:
place references
The `placeId` can be a direct reference to an external data source (i.e., data reuse). To support this, there's a dedicated endpoint: `/collections/datasources/items`.
This endpoint describes how to resolve external IDs, such as those starting with `"JL:StopPoint"`. These typically refer to entries in published NeTEx datasets.
Transmodel concepts
Terms like `"type: service"`, `"age group"`, and `"travel specification"` map directly to concepts and attributes defined in the Transmodel conceptual framework.
Requirements list
The list of requirements can be extensive — including, for example:
- Luggage
- PRM (Persons with Reduced Mobility) needs
- Bicycles
- Specific `ServiceJourney`
- A particular asset (e.g., a designated bike or seat)
In this simplified example, we've only added a single requirement: first-class service.
👉 If you're interested in the full set of information that can be passed when searching for offers, check out: Search offer request.
4W can respond with a collection of offers (just one shown):
{
"type": "OfferCollection",
"offers": [
{ "id": "5232f7f8-bfab-4330-a8ad-0eecefa3921f",
"type": "offer",
"properties": {
"legs": [...],
"products": [ { "type": "product",
"productId": "FIRST_CLASS_TICKET",
"productName": "First class single trip ticket",
"guarantees": [ { "id": "PASSENGER_SUPPORT", "type": "guarantee" } ] } ],
"price": { "amount": 3.62, "currencyCode": "EUR" },
"summary": { "refundable": true },
"expiryTime": "2025-05-13T12:55:17.193Z"
},
"links": [
{
"rel": "select-offers",
"description": "Select this offer",
"method": "POST",
"href": "/v1/processes/select-offers/execute",
"type": "application/json",
"body": {
"inputs": { "type": "select_offers",
"timestamp": "2025-05-14T13:23:25.004Z",
"offerIds": [ "5232f7f8-bfab-4330-a8ad-0eecefa3921f" ] }
}
}
]
}
],
"numberMatched": 1,
"numberReturned": 1,
"links": []
}Some key elements:
Consistent response structure
The response follows a fixed structure that includes:
- `type`
- `offers` (in other cases, this might be named `ancillaries` or some other plural name)
- `properties`
- `links`
This structure is reused across all responses, ensuring consistency.
Use of external data
External references are used again here.
For example, a `traveller ID` can also be an external identifier — such as a reference to an eIDAS 2.0 wallet or an account-based identity.
Link-driven process flow
The `links` object describes possible next steps.
In this case, the offer can be selected, resulting in a package (a SALES OFFER PACKAGE in Transmodel terminology) that can be purchased afterwards.
This approach allows operators to explicitly define the flow of their implementation — very convenient.
Explicit type declarations
Object types are often explicitly stated in the response to improve human readability.
Built-in paging support
For the technical audience: pagination is natively supported using properties like:
- `numberMatched`
- `numberReturned`
- links { `rel`: `next` }
- and links { `rel`: `prev` }
Clients can control pagination using OGCs default query parameters like `limit` and `offset`.
OGC APIs
This structured response pattern and process control are made possible through the use of OGC APIs.
This API specifically implements:
- OGC API - Records
- OGC API - Processes
- (and a minimal use of Features)
This STD shows the general process flows from OMSA. Most transitions correspond with an endpoint in the OMSA specification.

| Module | Transition | Endpoint | Description |
|---|---|---|---|
| ➡️ Offers | |||
| search offers | /processes/search-offers/execute | search for offer(s) | |
| ➡️ Pre-sales | |||
| select offers | /processes/select-offers/execute | select the offer(s) into a single package, ready to modify or to purchase | |
| update traveller | /processes/update-traveller/execute | update traveller (name, cards, licenses etc) | |
| remove traveller | /processes/update-traveller/execute | update traveller (name, cards, licenses etc) | |
| ➡️ Purchase | |||
| purchase offer | not implemented (yet) | directly purchase an offer, without configuring it | |
| purchase product | not implemented (yet) | directly purchase a ticket, or use a shared bike | |
| purchase package | /processes/purchase-package/execute | results in a pending package (not confirmed) | |
| release package | /processes/release-package/execute | release the selected offers in the package | |
| confirm package | /processes/confirm-package/execute | confirmation of the purchase | |
| rollback package | /processes/rollback-package/execute | the pending purchase is rolled back | |
| ➡️ After sales | |||
| extend expiry time | /processes/extend-expiry-time/execute | extend the purchase window | |
| claim refund option | /processes/claim-refund-option/execute | claim one of the refund options | |
| confirm refund option | /processes/confirm-refund-option/execute | confirm the option to refund |
Pattern: /processes/{operation}-{subject}/execute
As you can see, all endpoints have the same structure: 'processes', followed by an operation on a subject (like 'search' for 'offers') and 'execute'. This is result of the compliance with OGC API Processes. This meta-standard has a lot more advantages, like a standardized way to publish meta data of the complete implementation.
To support the transitions, there is also data required:
| Dataset | Endpoint | Description |
|---|---|---|
| Datasources | /collections/datasources/items | Specification of external datasources (like NeTEx) |
| Ancillaries | /collections/ancillaries/items | Applicable ancillaries for a package (set of offers) |
| Assets | /collection/ancillaries/items | Applicable assets for a leg (like seats, or bikes) |
| Travel documents | /collections/travel-documents/items | References to tickets, and ways to open shared modes |
| Refund options | /collections/refund-options/items | Refund options applicable for a travel, offer, ticket, ... |
Pattern: /collections/{collection name}/items
And again, a fixed structure: 'collections', followed by a collection name and 'items'. This time to comply with OGC API Records (and in case of ancillaries, even with OGC API Features, that returns geoJSON).
Is this usable for zonal tickets?
Absolutely. It is possible to deliver offers with only products, like zonal tickets. Even in the offer request, you can already specify the zones (if you have this information already up forehand).
Is it possible to use this for 'swipe-in-swipe-out' scenario?
Not yet, but due to the structure of the API (complying with OGC APIs), it is not hard to extend it. A /processes/swipe-in-product/execute or something like that can be arranged.
Relying on existing (local) data sources makes it hard to resell e.g. abroad. How would you address this?
OMSA focusses on standardizing the process, not the products. This opens up possibilities to migrate from existing, often peer-2-peer solutions to more commonly accepted specification, without breaking the bank.
We don't use a 2-step purchase process. Can OMSA facilitate a purchase, delivering directly a confirmed purchase?
It can, but we don't want to make a lot of dialects. Therefore, we prescribe to return a PENDING package, but in the links, you can specify that the 'confirm-package' link is not mandatory. This implies that when the rollback expiry time has expired, the package will be confirmed automatically. When the 'confirm-package' link is mandatory to execute, it implies that an explicit confirm is required, otherwise the pending package will expire.