Technical integration
The final step is to develop the client integration itself. This is typically done by the system provider. To call the Customs APIs you must
- Retrieve an access token from Maskinporten
- Exchange the Maskinporten token for a Customs token
- Call the Customs API with the Customs token
This requires that you have a valid certificate (from step 1) and have completed the necessary onboarding in step 2.
Scopes
Which scope(s) you should use depends on the services you will use, and requires
that Norwegian Customs has granted you access to that scope. Norwegian businesses use the
toll:… scopes, foreign businesses the corresponding
toll:eu/… scopes.
| API / service | Scope (Norwegian) | Scope (foreign) |
|---|---|---|
| movement-road-api-v2, movement-road-query-api-v2 | toll:movement/road/v2 | toll:eu/movement/road/v2 |
| movement-air-api, movement-air-query-api | toll:movement/air | toll:eu/movement/air |
| movement-rail-api, movement-rail-query-api | toll:movement/rail | toll:eu/movement/rail |
| movement-maritime-api, movement-maritime-query-api | toll:movement/maritime | toll:eu/movement/maritime |
| movement/presentation, movement/routing | toll:movement/entry | toll:eu/movement/entry |
| Document upload | toll:goodsdeclaration/document.write | toll:eu/goodsdeclaration/document.write |
| API for declaration data (not yet ready) | toll:declaration.feed | – |
Read more about scopes in Maskinporten here.
1. Retrieve an access token from Maskinporten
Norwegian businesses: see Maskinporten's documentation on how to retrieve an access token.
Foreign businesses: see Maskinporten's documentation under European eSeals, and follow the guide for European businesses.
The technical difference between Norwegian and foreign businesses
lies in how the client is identified: a
Norwegian business provides a registered client_id in iss, while a
foreign business is identified via the eSeal certificate in x5c
(and iss is ignored). The rest of the JWT grant is the same, except that
foreign businesses use the toll:eu/… scopes.
TIPS:
In addition to the documentation linked above, Digdir has also published a repository on GitHub
(jwt-grant-generator),
which demonstrates how to retrieve an
access token from Digdir's services, including Maskinporten.
The example applies to Norwegian businesses, but can be adapted for foreign businesses.
Note that its x5c must then be extended to the full certificate chain.
Setting up the JWT grant (summary)
The client creates a JWT grant, signs it with its key and POSTs it to the
/token endpoint in Maskinporten. The table shows some of the most important fields the grant should
contain, and what the differences are for Norwegian and foreign businesses.
| Field | Norwegian | Foreign (eSeal) |
|---|---|---|
Header: x5c |
Certificate used to sign the JWT grant (base64-encoded) | The certificate (eSeal) that signs the JWT grant, and the entire certificate chain (base64-encoded) |
aud |
https://maskinporten.no/(test: https://test.maskinporten.no/) |
Same |
iss |
Registered client_id (see Samarbeidsportalen) |
Name of business and/or system (Required, but the value is ignored.) |
scope |
toll:… (e.g. toll:movement/road/v2) |
toll:eu/… (e.g. toll:eu/movement/road/v2) |
iat / exp |
Required. exp at most 120 seconds after iat |
Same |
jti |
Recommended, unique value | Same |
The signed JWT grant is sent as assertion in a form-encoded POST
to Maskinporten's /token endpoint:
HTTP Request
POST /token Host: maskinporten.no (test: test.maskinporten.no) Content-Type: application/x-www-form-urlencoded grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer& assertion=<SIGNED JWT GRANT>
-
x5cmust be an array containing the entire certificate chain, not just the eSeal alone. See Maskinporten's own signing key (x5cclaim) as an example. -
Only scopes activated for European businesses can be used. You can
test with
digdir:verksemd.euin all environments, but it does not grant access to anything (and cannot be exchanged for a Customs token in step 2).
If the token request fails, see Maskinporten's troubleshooting page.
2. Exchange for a Customs-signed access token
Exchange Maskinporten's access token for a Customs-signed access token.
HTTP Request
POST /api/access/external/oauth/token Host: api.toll.no (test: api-test.toll.no) Content-Type: application/x-www-form-urlencoded grant_type=token-exchange& subject_token_type=access_token& subject_token=<MASKINPORTEN ACCESS TOKEN>
Example: HTTP Response
{
"issued_token_type": "access_token",
"access_token": "<TOLL ACCESS TOKEN HERE>",
"token_type": "Bearer",
"expires_in": 970
}
Tips:
A Customs-signed access token can be used for its entire lifetime, so it is recommended to
cache these when communicating with Norwegian Customs' APIs. For a smoother
experience, we further recommend exchanging for a new Customs token before the previous
one expires (for example one minute before).
Troubleshooting: an error such as “Not authorized to request scope toll:…” usually means the scope has not been granted to you. Check that:
- you are using the correct scope (Norwegian businesses use
toll:…, foreign businessestoll:eu/…); - the business is registered with Digitoll (see Onboarding);
- the integration in Samarbeidsportalen is complete and includes the scope (Norwegian businesses);
- if you are still unsure, confirm with Norwegian Customs which scopes you have been granted.
3. Test access to the API
This example applies to the road vehicle API.
HTTP Request
GET /api/movement/road/v2/test-auth Host: api.toll.no (test: api-test.toll.no) Authorization: Bearer <TOLL ACCESS TOKEN>
The expected result is 200 OK.