Authentication
Every call your system makes to ITS carries two proofs:
| Proof | What it shows | How you send it |
|---|---|---|
| An OAuth 2.0 access token | Which client is calling | Authorization: Bearer <access_token> on every call |
| A client certificate | That the connection comes from your registered system | Mutual TLS on every call to the gateway |
Your connection may also be limited to the source addresses you registered. A call from another address is refused at the edge.
What you have
Keys, certificates and receiving endpoints are issued in the ITS portal's System integration workspace once the Secretariat admits your organisation.
| Item | Where it comes from |
|---|---|
| Client ID | Generated in the workspace. One confidential client per organisation per environment. |
| Client secret | Shown once, when you generate or rotate the credential. |
| Client certificate and private key | Your own. You register the certificate in the workspace. |
| Token URL | The Environments page. |
| Gateway base URL | The Environments page. |
A step-by-step onboarding guide for the workspace is coming.
The examples in these guides read everything from shell variables, never from literal values:
| Variable | Holds |
|---|---|
ITS_TOKEN_URL | The token URL for your environment |
ITS_GATEWAY_URL | The gateway URL your System integration workspace shows for the environment, ending in /api/v1. It is the gateway host on the Environments page followed by /api/v1. |
ITS_CLIENT_ID | Your client ID |
ITS_CLIENT_SECRET | Your client secret |
ITS_CLIENT_CERT | The path to your client certificate file |
ITS_CLIENT_KEY | The path to your certificate's private key file |
ITS_TOKEN | The access token you got from the token URL |
Paths in these guides, such as /etd/{serial_no}, are relative to the gateway base URL.
Get a token
Send a client credentials request to the token URL. The client ID and secret go in HTTP Basic authentication.
curl --fail-with-body --silent --show-error \
--user "$ITS_CLIENT_ID:$ITS_CLIENT_SECRET" \
--data "grant_type=client_credentials" \
"$ITS_TOKEN_URL"
A successful response is HTTP 200:
{
"access_token": "<access_token>",
"expires_in": 300,
"token_type": "Bearer"
}
The values above are examples. Always use the expires_in your response carries.
- Keep the token in memory and reuse it. Do not request a token for every call.
- Renew it about 60 seconds before it expires.
- There is no refresh token. To renew, repeat the same request.
Call the gateway
Send the token and present your certificate on every call:
curl --fail-with-body --silent --show-error \
--cert "$ITS_CLIENT_CERT" --key "$ITS_CLIENT_KEY" \
--header "Authorization: Bearer $ITS_TOKEN" \
--header "Accept: application/json" \
"$ITS_GATEWAY_URL/etd?page-number=1&page-size=1"
This reads one page of the declarations you may see. It is safe to run before any declaration exists, and in Sandbox it is your first certification milestone.
Every response is wrapped in an envelope with a header and data:
{
"header": {
"operation": "GET",
"request_uri": "/api/v1/etd",
"response_code": 200,
"response_message": "SUCCESS",
"additional_details": "",
"correlation_id": "d4d599fa-..."
},
"data": [],
"pagination": {
"page_number": 1,
"page_size": 1,
"total_pages": 0,
"total_records": 0
}
}
Keep the correlation_id. It is how you look up a refusal afterwards. See Errors and troubleshooting.
Who ITS thinks you are
ITS resolves your organisation from the credential's binding, which the Secretariat controls. It never reads a country from a token claim or from your request. There is nothing to set, and sending a country claim changes nothing.
From that binding ITS works out two sets of countries:
- the countries whose declarations you may see;
- the countries you may act for — lodge, stamp, amend, cancel.
| Caller | Sees | Acts for |
|---|---|---|
| State Party | Declarations its country filed, will receive, has stamped, or has been named next for | Its own country, once the Secretariat records its participation |
| REC | What any of its members may see | Only the members that delegated submission to it |
| AfCFTA Secretariat | Every declaration | Nobody |
GET /etd/scope tells you what your credential may see and act for. It describes the entitlement and confers none: ITS checks every call on its own.
A permission and an entitlement are different checks. A permission says what kind of act your client may perform. An entitlement says on whose behalf. A REC can hold the permission to stamp and still be refused a stamp for a country that has not delegated to it.
Permissions by endpoint
Endpoints are deny by default. Each needs a permission your client holds.
| Permission | Endpoints |
|---|---|
etd:issue | POST /etd |
etd:read | Every read of a declaration, the list, /etd/reconciliation/*, /etd/rejections, attachment list and download, the PDFs |
etd:endorse | POST /etd/{serial_no}/endorsements/{entry|depart|exit|discharge} |
etd:amend | POST /etd/{serial_no}/versions |
etd:cancel | POST /etd/{serial_no}/cancel |
etd:attach | POST /etd/{serial_no}/attachments |
etd:acknowledge | POST /etd/{serial_no}/deliveries/ack |
notification:read | GET /notifications and its sub-paths |
notification:write | POST /notifications/{sequence_no}/read and POST /notifications/read-all |
GET /etd/scope and reference data need a valid token and no permission. See Reference data.
When you are refused
A call with no token is not refused the same way everywhere:
| Path | Answer to a call with no token |
|---|---|
/api/reference/... | 401 |
Everything under the gateway base URL, such as /etd | 403 |
Both mean you were not authenticated. Do not branch on 401 alone. On either:
- Get a new token.
- Retry the call once.
- If it is refused again, check that the
Authorizationheader is actually being sent before you look at permissions.
Three other refusals are common while you set up:
| Code | Meaning | What to do |
|---|---|---|
AUTHZ-ETD-014 | Your credential cannot be attributed to an organisation. | Contact the Secretariat. The credential is not bound. |
AUTHZ-ETD-017 | No submission arrangement is recorded for your organisation. | The Secretariat must record your participation or delegation first. |
AUTHZ-ETD-016 | An arrangement exists and does not cover this country. | Check which country you are acting for. |
A TLS or certificate error is a connection problem, not an authorisation one. Never turn off certificate checks, and never use cURL's -k option.
Protect your credentials
- Load the client secret and private key from a protected secret manager or protected environment variables.
- Send the client secret only to the token URL. Never send it to the gateway.
- Treat the access token as sensitive too. Do not log it.
- Rotate a credential in the workspace if it may have been exposed. Rotation is self-service.
After a rotation, update your secret store, get a new token with the new credential and make one call to check it works.
Do not rotate credentials automatically when the token URL or the gateway returns a 5xx error. Those errors are temporary: retry with backoff instead.