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

  1. Retrieve an access token from Maskinporten
  2. Exchange the Maskinporten token for a Customs token
  3. 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>
Tips (foreign businesses):
  • x5c must be an array containing the entire certificate chain, not just the eSeal alone. See Maskinporten's own signing key (x5c claim) as an example.
  • Only scopes activated for European businesses can be used. You can test with digdir:verksemd.eu in 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:

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.