Introduction
CerQlar provides a REST API to interact with the platform. Our APIs have predictable resource-oriented URLs, accept JSON-encoded request bodies, return JSON-encoded responses, and use standard HTTP response codes, authentication, and verbs.
Authentication
Every request to the CerQlar REST API needs to be authenticated with an Authorization
header.
The header should take the form Authorization: Bearer {bearer_token}
where {bearer_token}
is replaced with a
valid token. To request a new token, make a call to the /authentication/oauth/token
endpoint. You can find more details and try it out on the Administration API page.
Your request would look like:
POST https://{environment}/administration/v1/authentication/oauth/token
Content-Type: application/json
{
"client_id": "{client_id}",
"client_secret":"{client_secret}",
"audience": "cerqlar-backend",
"grant_type": "client_credentials"
}
Where:
-
{environment}
will be either-
api.demo.cerqlar.com
for the demo environment or -
api.cerqlar.com
for production
-
-
{client_id}
and{client_secret}
are your API key credentials. New API keys can be created here for demo or here for production. If you see an error message when accessing this page, please contact support. Make sure to always keepclient_secret
secure. If you think it may have been compromised, delete your API key and create a new one.
After executing the request, you should receive a response back which looks something like this:
{
"access_token": "{bearer_token}",
"scope": ["trade:capture", "trade:list", ...],
"expires_in": 86461,
"token_type": "Bearer"
}
The value {bearer_token}
can now be used in the Authorization
header. This token can be used for multiple
requests, but will expire after some number of seconds, indicated by the expires_in
field. After this, a new token
will need to be requested.
Rate limiting
CerQlar rate limits the REST API based on the client_id
you provided when calling the /authentication/oauth/token
endpoint. The current rate limits are:
-
3 requests per hour for the
/authentication/oauth/token
endpoint which issues access tokens. Docs for this endpoint can be found on the Administration API page -
10 requests per second for all other endpoints combined
Up-to-date information about rate limits can also be found in the response headers:
-
x-ratelimit-limit
- indicates the request-quota associated to the client in the current time-window followed by the description of the quota policy. E.g.3, 3;w=60
- 3 requests in your current quota, and then the description of the quota policy - 3 requests per 60 seconds -
x-ratelimit-remaining
- indicates the remaining requests in the current time-window -
x-ratelimit-reset
- indicates the number of seconds until reset of the current time-window
If you have exceeded your quota, the response status will be 429 Too Many Requests
.
APIs
Trading API
API for trade lifecycle management, including payments and deliveries.
Inventory API
API for retrieving transactions and managing transaction attributions
Datatypes
Dates and times
Dates and times always use ISO 8601 with a UTC timezone (where applicable).
Examples:
-
2023-01-01
is 1st January 2023 -
2023-01-01T12:00:00.000Z
is midday UTC on 1st January 2023 -
2023-01
is January 2023
Enumerations
In the CerQlar API, there are two types of enumerations; standard and extensible.
Standard enumerations
A standard enumeration is a closed set of values. Changes to these enumerations are considered a breaking change. Should we need to make a change to a standard enumeration, we will bump the API version.
Extensible enumerations
Values in an extensible enumeration are open-ended. We may add new values without notice and this is not
considered a breaking change. In the API docs and our OpenAPI schemas, extensible enums appear as
x-extensible-enum
. This is a non-standard extension and will be ignored by most code generation tools.
Important
|
You must ensure that any client code can handle new values which are returned from the CerQlar API, i.e. it must implement a fallback for any values which were unknown at the time of writing the client. |
Searching
There are several search endpoints in the CerQlar API, all of which follow the same pattern. A search request is made by sending a POST request to a search endpoint with a body containing the search criteria. For example, the following request will search for buy trades where the deal date is greater than 2023-01-01 and sort the results by when the trade was last modified. The first 100 results will be returned:
-
Trading v1
-
Trading v2
POST /trading/v1/trades/search
Content-Type: application/json
Authorization: Bearer {bearer_token}
{
"query": {
"filter": {
"and": [
{
"field": "side",
"operator": "EQUALS",
"value": "BUY",
"_type": "string"
},
{
"field": "dealDate",
"operator": "GREATER_THAN",
"value": "2023-01-01",
"_type": "date"
}
]
},
"sort": [
{
"field": "lastChangedAt",
"direction": "DESC"
}
],
"page": {
"pageNumber": 0,
"pageSize": 100
}
}
}
POST /trading/v2/trades/search
Content-Type: application/json
Authorization: Bearer {bearer_token}
{
"query": {
"filter": {
"and": [
{
"field": "side",
"operator": "EQUALS",
"value": "BUY",
"_type": "string"
},
{
"field": "dealDate",
"operator": "GREATER_THAN",
"value": "2023-01-01T12:00:00Z",
"_type": "datetime"
}
]
},
"sort": [
{
"field": "lastChangedAt",
"direction": "DESC"
}
],
"page": {
"pageNumber": 0,
"pageSize": 100
}
}
}
Response 200 OK:
{
"items": [
{
...
},
...
],
"page": {
"pageSize": 100,
"pageNumber": 0,
"totalItems": 7,
"totalPages": 1
}
}
All the fields within the query
object of the request are optional. In this case no filtering or sorting are
applied and the zeroth page containing the first 10 elements will be returned:
{
"query": {
}
}
More examples are provided here.
Note
|
Not all fields can be used in filter and sort clauses. The list of fields which search endpoints support is available in the api docs. |
Filtering
Field matching
Results can be filtered based on field values. For example:
{
"field": "side",
"operator": "EQUALS",
"value": "BUY",
"_type": "string"
}
Where:
-
field
is the name of the field to filter on. Where the field is in a nested object, field names can be joined into a path using.
to separate fields, e.g.tradeDetails.technologies
. -
operator
values includeEQUALS
,NOT_EQUALS
,GREATER_THAN
,GREATER_THAN_OR_EQUAL
,LESS_THAN
,LESS_THAN_OR_EQUAL
andCONTAINS
.CONTAINS
is only valid for string type fields. -
value
is the value to filter against. -
_type
is the type of the value passed, one ofstring
,long
,date
ordatetime
.
It’s also possible to check if a field contains one of a set of values:
{
"field": "clientUserId",
"operator": "IN",
"values": ["alice", "bob"],
"_type": "string"
}
The operator must be either IN
or NOT_IN
.
Logical operators
Filters can also be combined using and
or or
operators, for example:
{
"and": [
{
"field": "",
...
},
{
"field": "",
...
}
]
}
Sorting
Results can be sorted on zero or more fields. When no sorting criteria are provided, the results are unsorted.
Paging
Results are always paged. Page numbers are zero based. When the page
field is not provided, the default value is used
which is to return the zeroth page with a page size of 10. In the response, the total number of items matching
the query and the number of pages available are returned.
Warning
|
If the contents of the database are modified while results are being paged through, the values of |
Examples
Search for counterparties with a legalName
equal to Acme
, returning the first 10 results and without any sorting:
POST /administration/v1/counterparties/search
Content-Type: application/json
Authorization: Bearer {bearer_token}
{
"query": {
"filter": {
"field": "legalName",
"operator": "EQUALS",
"value": "Acme",
"_type": "string"
}
}
}
Search for trades where the issuing countries include Belgium and the Netherlands and sort the results by when the trade was last updated, newest first:
-
Trading v1
-
Trading v2
POST /trading/v1/trades/search
Content-Type: application/json
Authorization: Bearer {bearer_token}
{
"query": {
"filter": {
"field": "tradeDetails.issuingCountrySpec.countries",
"operator": "IN",
"values": ["NL","BE"],
"_type": "string"
},
"sort": [
{
"field": "lastChangedAt",
"direction": "DESC"
}
]
}
}
POST /trading/v2/trades/search
Content-Type: application/json
Authorization: Bearer {bearer_token}
{
"query": {
"filter": {
"field": "productSpecification.GO.issuingCountry.countries.include.values",
"operator": "IN",
"values": ["NL","BE"],
"_type": "string"
},
"sort": [
{
"field": "lastChangedAt",
"direction": "DESC"
}
]
}
}
Get the 50 most recently updated trades:
-
Trading v1
-
Trading v2
POST /trading/v1/trades/search
Content-Type: application/json
Authorization: Bearer {bearer_token}
{
"query": {
"sort": [
{
"field": "lastChangedAt",
"direction": "DESC"
}
],
"page": {
"pageSize": 50,
"pageNumber": 0
}
}
}
POST /trading/v2/trades/search
Content-Type: application/json
Authorization: Bearer {bearer_token}
{
"query": {
"sort": [
{
"field": "lastChangedAt",
"direction": "DESC"
}
],
"page": {
"pageSize": 50,
"pageNumber": 0
}
}
}
Patching
The patch endpoints can be used to modify an entity. These are RFC-6902 compliant JSON patch endpoints. A description of how JSON patch works is here.
Note that not all fields can be patched. The list of fields which can be patched is available in the api docs.
Important
|
It’s important to understand that patches are always applied to the entity as it appears in the GET endpoint for that entity. For example, when patching a trade, the |
Examples
Updating fields on a trade
The following call will update trade ABC123
such that the clientTradeId
becomes trade-123
and
the code T0201
is added to the existing list of technologies in trade details.
-
Trading v1
-
Trading v2
PATCH /trading/v1/trades/ABC123
Content-Type: application/json
Authorization: Bearer {bearer_token}
[
{
"op":"replace",
"path":"/clientTradeId",
"value": "trade-123"
},
{
"op": "add",
"path": "/tradeDetails/technologies/-",
"value": "T0201"
}
]
PATCH /trading/v2/trades/ABC123
Content-Type: application/json
Authorization: Bearer {bearer_token}
[
{
"op":"replace",
"path":"/clientTradeId",
"value": "trade-123"
},
{
"op": "add",
"path": "/productSpecification/GO/technology/values/-",
"value": "T0101"
}
]
Replacing the product specification on a trade
It is possible to replace a whole object at once rather than individual fields. For example, the whole product specification can be replaced like this:
-
Trading v1
-
Trading v2
PATCH /trading/v1/trades/ABC123
Content-Type: application/json
Authorization: Bearer {bearer_token}
[
{
"op":"replace",
"path": "/tradeDetails",
"value": {
"technologies": ["T01"],
"plantCommissioningDate": "2002-01-01"
}
}
]
PATCH /trading/v2/trades/ABC123
Content-Type: application/json
Authorization: Bearer {bearer_token}
[
{
"op": "replace",
"path": "/productSpecification/GO",
"value": {
"technology": {
"values": ["T01"]
},
"plantCommissioningDate": {
"start": {
"value": "2002-01-01"
}
}
}
}
]
Update volume on a production period
To update the trade volume for a production period, the trade should be patched like this:
-
Trading v2
Note that in the below request, the volume needs to be in watt-hours, not MWh. Also {n}
and {m}
in the
path should be replaced by integers which corresponds to the position the delivery and product period
you want to update appear in when you GET
the trade. These positions are zero indexed, so the first delivery would
be 0, the second 1, etc.
PATCH /trading/v2/trades/ABC123
Content-Type: application/json
Authorization: Bearer {bearer_token}
[
{
"op": "replace",
"path": "/schedule/{n}/productionPeriods/{m}/volume/value",
"value": 200000000
}
]
Change delivery method
When changing between cancellation and transfer delivery methods, it is recommended to replace the whole
deliveryDestination
object as there are some fields which exists on both cancellation and
transfer details. Since the two delivery methods have different validation rules, patching individual fields within
the deliveryDestination object can lead to confusion.
-
Trading v2
PATCH /trading/v2/trades/ABC123
Content-Type: application/json
Authorization: Bearer {bearer_token}
[
{
"op": "replace",
"path": "/deliveryDestination",
"value": {
"deliveryMethod": "CANCEL",
"registryId": "ATOS",
"usageCategory":"DISCLOSURE"
}
}
]
Changelog
Trading v2
The trades v2 API is available under the path /trading/v2
.The most significant change is the addition of
multi-product support (i.e. products other than GOs), however other changed have also been made.
Multi-product support
In the trade response, the tradeDetails
field has been replaced by productSpecification
. Inside the product
specification is another object where the key is the product type (GO
, IREC
, etc) and the value is an object.
Which fields can appear within this object depend on the product type. All fields are optional.
In the case of GOs, the same set of fields are available as in tradeDetails, however some changes have been made to the field names and the structure of the values. See the API docs for more details.
{
// ...
"productSpecification": {
"GO": { // Product type of this trade
"typesOfSupport": { // The fields which can appear here depend on the product type.
"values": [
"INVESTMENT"
]
},
"technology": {
"values": ["T01"]
}
}
},
// ...
}
Sometimes a field with the same name can appear in more than one product type. For example, both GOs and I-RECs have a
technology
field, however these are different fields with a different set of acceptable values.
Searching
When searching for trades, it is now necessary to list the product types you wish to retrieve using the productType
query parameter. This is required so that as more product types are added to the API in the future, existing
requests won’t automatically receive those new trade types in the response. The productType
query param can accept
multiple values separated by commas.
POST /trading/v2/trades/search?productType=GO,IREC
In the body of the request, the filtering and sorting paths must reflect the new structure of trades. For example in
v1 the path to the technology field was tradeDetails.technologies
but in v2, it is now
productSpecification.GO.technology.values
. If you want to filter on I-REC technologies instead of GO ones, then the
path would become productSpecification.IREC.technology.values
.
Volumes and units
The volume field on the production period has changed from a simple number with an implicit unit of watt-hours to an object.
{
"volume": {
"value": 1000000,
"unit": "WATT_HOUR"
}
}
When filtering on this field, use a numeric value in watt-hours as in API v1.
Date only fields
dealDate
, paymentDue
and deliveryDue
fields have all been converted from date only fields to datetime fields.
As with all the other datetime fields in the API, the timezone is always UTC. The values should be given in ISO format,
for example 2024-07-05T11:34:00Z
.
Other changes
-
The
PUT /trading/v1/production-periods/{id}/volume
endpoint has been removed in v2. The same can be achieved usingPATCH /trading/v2/trades/{tradeId}
-
The
deliveryMethod
field on trades, which was already deprecated in v1, has been removed completely and replaced bydeliveryDestination
-
The
delivery
field on trade schedules has been renamed todeliveryObligation
-
The
deliveredAt
anddeliveryConfirmedAt
fields on deliveries have been renamed tosellerSettledAt
andbuyerSettledAt
respectively -
The
expectedAmount
field on payments has been removed. -
The
deliveryId
field on payments has been removed. The fieldschedule.{n}.delivery.id
can be used instead -
Only the IDs of GO certifications are supported in v2, display names are no longer allowed. For example in v1 both
TÜV NORD CERT
andTUV_NORD_CERT
were allowed, but in v2 onlyTUV_NORD_CERT
is