TransFollow Walkthrough

This chapter helps you connect to the TransFollow API. If you are developing for Android and are looking to integrate TransFollow into your application you should read about using the integration interface of our TransFollow Android app.

When you want to start with TransFollow a successful implementation on the partner environment is required. The steps you are going through are these:

  1. Register at TransFollow
  2. Implement the interface
  3. Test the implementation
  4. Notify TransFollow about the implemented functionality
  5. Move your application to production.

1. The Partner Environment

For developers TransFollow B.V. provides a partner environment with a separate webportal and mobile application. The partner environment is a separate system specifically available for enablers. They can develop and test their implementations without the danger of causing harm to the live system - and without any cost. Small tests with customers can be done as well. The partner API and backend are functionally equal to our production environment, albeit with a lower availability guarantee and not producing legal CMR’s.

The Partner API is at https://partner.transfollow.com/api/, the portal is at https://portal.partner.transfollow.com/. On this portal you will be able to view freight documents, order credits and update your account information.

You must use this environment for demonstrations, testing and development.

The latest production API is at https://api.transfollow.com/api/. (Specific versions are at https://api.transfollow.com/$VERSION/api/ where $VERSION is an API version number, currently 'v1'.)

The production webportal is available at https://portal.transfollow.com.

2. Get Authorized

In order for your application to use the TransFollow API, you will need to register with the TransFollow services as a new user and request an API Key for the partner environment of TransFollow. The API key consists of an OAUTH client id and secret. The client id and secret are client system specific and will be used to authenticate your client (under development) when a user requests an access token. This access token is account specific and valid for a limited time. The access token needs to be sent with every API call and it is analyzed by TransFollow to identify the end user and to authorize the request. All requests must be encrypted by TLS/SSL.

Follow these steps to get started:

Register as a user with TransFollow

You will need to use the TransFollow partner webportal https://portal.partner.transfollow.com/ to register as a new account. (Your user name and password will be represented as {username} and {password} in this walkthrough.)

Request a new OAUTH client id and secret

After confirming your user account you also must request a client id and secret by contacting our implementation support team: please send a request to support@transfollow.org. They will issue a new client id and client secret. Save these values securely for later use. The client id and secret combination is also referred to as an API key. For the production environment (later) you will need an API key separate from the partner environment.

TransFollow API server

The TransFollow partner API server's is at https://partner.transfollow.com/api/. All endpoints described in the API spec are reachable relative to the base path /api/.

To follow the next steps preferably use a tool like Postman to easily configure and execute HTTP requests.

A preconfigured Postman configuration set for the TransFollow API in the Partner environment is available in a zip file. Get Postman here: https://getpostman.com

Get an Access Token

Before you can use most endpoints you will need to login with your account, and thus obtain an access token. Access tokens have a relatively short expiration time, refresh tokens (see below) are valid for a longer time. The exact POST request, as included in the Postman examples, looks like this:

POST /api/oauth/token HTTP/1.1
Host: partner.transfollow.com
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Authorization: Basic dHJ1c3RlZC1jbGllbnQ6c29tZXNlY3JldA==
Content-Length: 86

grant_type=password&username={username}&password={password}&scope=transfollow

Replace {username} and {password} with your TransFollow username and password. The HTTP POST request also requires a standard Basic Authorization header line with the base-64 encoded version of the client id and client secret. Base-64 encode the following string, including the colon separating your client id and client secret. Replace the client id and client secret with your values. Don't include the braces.

{clientid}:{clientsecret}

After encoding, add the encoded string to a Basic Authorization line in the HTTP header: Authorization: Basic dHJ1c3RlZC1jbGllbnQ6c29tZXNlY3JldA==

Below is a sample curl command to the same endpoint:

curl 'https://partner.transfollow.com/api/oauth/token'
    -H 'Accept: application/json'
    -H 'Authorization: Basic dHJ1c3RlZC1jbGlabcdef9tZXNlY3JldA=='
    -H 'Content-Type: application/x-www-form-urlencoded'
    --data 'grant_type=password&username=user@example.com&password=user-password&scope=transfollow'
    --compressed

Replace the username and password values and the base-64 string with your data and this call should return the same response as below:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "access_token": "49ec4359-31e6-4d63-852c-02c1d9b7d8e9",
  "token_type": "bearer",
  "refresh_token": "48691eb6-8192-42fd-8a07-8d18b2ad3c82",
  "expires_in": 43199,
  "accountSecret": 532496794312690300000,
  "scope": "transfollow"
}

Access and Refresh tokens with additional information are returned, as well as an account secret. This is an account specific number used for signing freight documents.

Get a new token pair with a Refresh Token

An access token has a certain time to live, after which it becomes invalid. You will notice this if a call to an endpoint returns http status 401, with error code '1.11'. With the refresh token you can obtain a new set of tokens using a second version of the login call.

POST /api/oauth/token HTTP/1.1
Host: partner.transfollow.com
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Authorization: Basic dHJ1c3RlZC1jbGllbnQ6c29tZXNlY3JldA==
Content-Length: 93

grant_type=refresh_token&refresh_token={refresh token}&scope=transfollow

Replace {refresh token} with the value of "refresh_token" from the login response. The HTTP call again requires the standard Basic Authorization line with the identical base-64 encoded string of the client id and key. Below is a sample curl to get a token:

curl 'https://partner.transfollow.com/api/oauth/token'
    -H 'Accept: application/json'
    -H 'Authorization: Basic dHJ1c3RlZC1jbGlabcdef9tZXNlY3JldA=='
    -H 'Content-Type: application/x-www-form-urlencoded'
    --data 'grant_type=refresh_token&refresh_token=48691eb6-8192-42fd-8a07-8d18b2ad3c82&scope=transfollow'
    --compressed

The response message:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "access_token": "49ec4359-31e6-4d63-852c-02c1d9b7d8e9",
  "token_type": "bearer",
  "refresh_token": "48691eb6-8192-42fd-8a07-8d18b2ad3c82",
  "expires_in": 43199,
  "scope": "transfollow"
}

3. Start Using the API

You can now use the returned access token to call other endpoints. You will need to add a Bearer Authorization header containing the access token for each call. For example, call the /accounts/users/me endpoint without any parameters to get your account information:

curl 'https://partner.transfollow.com/api/accounts/users/me'
    -H 'Authorization: Bearer 49ec4359-31e6-4d63-852c-02c1d9b7d8e9'
    -H 'Accept: application/json'

You will need to replace the token '49ec4359-31e6-4d63-852c-02c1d9b7d8e9' above with your own token. For a full description of this endpoint and all other available endpoints, again see the API specifications.

4. Create Your First Freight Document

At first use a tool like Postman to configure and execute a HTTP POST request that creates your freight documents. Later embed these calls into your own code. Our Postman configuration set for TransFollow contains examples, but here are the basics.

Your first POST request should look like this:

POST /api/freightdocuments HTTP/1.1
Host: partner.transfollow.com
Accept: application/json
Content-Type: application/json
Authorization: Bearer dHJ1c3RlZC1jbGllbnQ6c29tZXNlY3JldA==
Content-Length: 2684

{
  "attachments": [],
  "carrier": {
    "buildingNumber": 6,
    "cityName": "Amsterdam",
    "contactEmailAddress": null,
    "contactName": null,
    "contactNote": null,
    "contactPhoneNumber": null,
    "countryCode": "NLD",
    "countryName": "Nederland",
    "name": "Trucking Company",
    "postalBox": null,
    "postalCode": "3333 AA",
    "streetName": "Anystreet",
    "submittedAccountEmailAddress": "carrier@example.com"
  },
  "consignee": {
    "buildingNumber": 7,
    "cityName": "Amsterdam",
    "contactEmailAddress": null,
    "contactName": null,
    "contactNote": null,
    "contactPhoneNumber": null,
    "countryCode": "NLD",
    "countryName": "Nederland",
    "name": "Receiving Company",
    "postalBox": null,
    "postalCode": "3333 AA",
    "streetName": "Anystreet",
    "submittedAccountEmailAddress": "consignee@example.com"
  },
  "consignor": {
    "buildingNumber": 7,
    "cityName": "Rotterdam",
    "contactEmailAddress": null,
    "contactName": null,
    "contactNote": null,
    "contactPhoneNumber": null,
    "countryCode": "NLD",
    "countryName": "Nederland",
    "name": "Sending Company",
    "postalBox": null,
    "postalCode": "1111 AA",
    "streetName": "Anystreet",
    "submittedAccountEmailAddress": "consignor@example.com"
  },
  "placeOfTakingOver": {
    "buildingNumber": null,
    "submittedAccountNumber": null,
    "cityName": "Rotterdam",
    "contactEmailAddress": null,
    "contactName": null,
    "contactNote": null,
    "contactPhoneNumber": null,
    "countryCode": "NLD",
    "countryName": "Nederland",
    "name": "Depot",
    "postalBox": null,
    "postalCode": null,
    "streetName": null,
    "submittedAccountEmailAddress": null
  },
  "agreedDateTimeOfTakingOver": "2017-04-11T10:36:13Z",
  "agreedDateTimeOfDelivery": "2017-04-11T10:36:13Z",
  "estimatedDateTimeOfTakingOver": "2017-12-29T00:00:00.000Z",
  "estimatedDateTimeOfDelivery": "2017-12-30T00:00:00.000Z",
  "establishedDate": "2017-12-01",
  "establishedPlace": "Anyplace",
  "reimbursementCurrency": "EUR",
  "reimbursementAmount": 1.00,
  "paymentForCarriage": "1000EUR",
  "structuredGoods": [
    {
      "type": "GOOD",
      "name": "First article",
      "ssccCode": null,
      "numberOfPackages": 1,
      "internalCode": null,
      "shortName": null,
      "eanCode": null,
      "methodOfPackaging": null,
      "natureOfGoods": null,
      "statisticalNumber": 0,
      "weight": 4.5,
      "weightUnit": "KILOGRAM",
      "volume": 4.5,
      "volumeUnit": "LITRE",
      "commercialValue": null,
      "currency": null
    }
  ],
  "senderInstructions": "None",
  "specialAgreements": "",
  "transportConditions": "AVC2002",
  "references": [],
  "status": "DRAFT",
  "type": "WAYBILL"
}

For more examples of valid JSON content for creating a freight document see the API specs. The response with HTTP status 201 will contain the freightDocumentId of the newly created freight document.

A subsequent GET /api/freightdocuments/{id} will show you the current state of you freight document. Here {id} must be replaced by the freightDocumentId (in this case 23570):

GET /api/freightdocuments/23570 HTTP/1.1
Host: partner.transfollow.com
Accept: application/json
Authorization: Bearer dHJ1c3RlZC1jbGllbnQ6c29tZXNlY3JldA==

This call will result in the following response:

{
    "agreedDateTimeOfTakingOver": "2017-04-11T10:36:13Z",
    "agreedDateTimeOfDelivery": "2017-04-11T10:36:13Z",
    "approvals": [],
    "attachments": [],
    "authorityReference": "000000025770",
    "availableSigningMethods": {
        "collection": [
            "OWN",
            "TFA"
        ],
        "carrierToCarrier": [
            "OWN",
            "TFA"
        ],
        "delivery": [
            "SIGN_ON_GLASS",
            "OWN",
            "TFA"
        ]
    },
    "carrier": {
        "accountId": null,
        "buildingNumber": "6",
        "cityName": "Amsterdam",
        "contactEmailAddress": "",
        "contactName": "",
        "contactNote": "",
        "contactPhoneNumber": "",
        "countryCode": "NLD",
        "countryName": "Nederland",
        "glnCode": "",
        "name": "Trucking Company",
        "postalBox": "",
        "postalCode": "3333 AA",
        "roleId": "132195",
        "streetName": "Anystreet",
        "submittedAccountEmailAddress": "carrier@example.com"
    },
    "carrierCode": "",
    "comments": [],
    "consignee": {
        "accountId": null,
        "buildingNumber": "7",
        "cityName": "Amsterdam",
        "contactEmailAddress": "",
        "contactName": "",
        "contactNote": "",
        "contactPhoneNumber": "",
        "countryCode": "NLD",
        "countryName": "Nederland",
        "glnCode": "",
        "name": "Receiving Company",
        "postalBox": "",
        "postalCode": "3333 AA",
        "roleId": "132193",
        "streetName": "Anystreet",
        "submittedAccountEmailAddress": "consignee@example.com"
    },
    "consignor": {
        "accountId": null,
        "buildingNumber": "7",
        "cityName": "Rotterdam",
        "contactEmailAddress": "",
        "contactName": "",
        "contactNote": "",
        "contactPhoneNumber": "",
        "countryCode": "NLD",
        "countryName": "Nederland",
        "glnCode": "",
        "name": "Sending Company",
        "postalBox": "",
        "postalCode": "1111 AA",
        "roleId": "132194",
        "streetName": "Anystreet",
        "submittedAccountEmailAddress": "consignor@example.com"
    },
    "deliveryDate": null,
    "establishedDate": "2017-12-01",
    "establishedPlace": "Anyplace",
    "estimatedDateTimeOfDelivery": "2017-12-30T00:00:00Z",
    "estimatedDateTimeOfTakingOver": "2017-12-29T00:00:00Z",
    "freightDocumentId": "23570",
    "goods": "________________________________________________________________________________________________________________________________________________________________________________________\n| Name         | SSCC-Code| Number of Packages| Internal Code| Short Name| EAN-Code| Method of Packaging| Nature of Goods| Statistical Number| Weight      | Volume   | Commercial Value|\n|=======================================================================================================================================================================================|\n| First article| N/A      | 1                 | N/A          | N/A       | N/A     | N/A                | N/A            | 0                 | 4.5 KILOGRAM| 4.5 LITRE|                 |\n",
    "lastModifiedDateTime": "2017-10-02T13:38:40.281Z",
    "ownDelegates": [],
    "ownPermissions": [
        {
            "permissions": [
                "UPDATE",
                "VIEW_SEALED_ATTACHMENT",
                "VIEW",
                "DELEGATE"
            ],
            "role": "SUBMITTER",
            "roleId": "132192"
        }
    ],
    "paymentForCarriage": "1000EUR",
    "placeOfDelivery": null,
    "placeOfTakingOver": {
        "accountId": null,
        "buildingNumber": "",
        "cityName": "Rotterdam",
        "contactEmailAddress": "",
        "contactName": "",
        "contactNote": "",
        "contactPhoneNumber": "",
        "countryCode": "NLD",
        "countryName": "Nederland",
        "glnCode": "",
        "name": "Depot",
        "postalBox": "",
        "postalCode": "",
        "roleId": "132196",
        "streetName": "",
        "submittedAccountEmailAddress": ""
    },
    "previousCommits": [
        "67887",
        "67888"
    ],
    "references": [],
    "reimbursementAmount": 1,
    "reimbursementCurrency": "EUR",
    "senderInstructions": "None",
    "specialAgreements": "",
    "status": "DRAFT",
    "structuredGoods": [
        {
            "type": "GOOD",
            "commercialValue": null,
            "currency": null,
            "eanCode": null,
            "internalCode": null,
            "methodOfPackaging": null,
            "name": "First article",
            "natureOfGoods": null,
            "numberOfPackages": 1,
            "productId": 1,
            "shortName": null,
            "ssccCode": null,
            "statisticalNumber": 0,
            "volume": 4.5,
            "volumeUnit": "LITRE",
            "weight": 4.5,
            "weightUnit": "KILOGRAM"
        }
    ],
    "structuredGoodsHeader": {
        "commitId": "67887",
        "createDateTimeClient": "2017-10-02T13:38:40Z",
        "createDateTimeServer": "2017-10-02T13:38:40Z",
        "previousCommits": []
    },
    "submitterAccountId": "28768",
    "submitterName": "Submitter",
    "subsequentCarriers": [],
    "transFollowNumber": "000000025770",
    "transportConditions": "AVC2002",
    "type": "WAYBILL",
    "updates": [
        {
            "account": {
                "accountId": "28768",
                "accountNumber": "00028894",
                "email": "frankmob+submitter@gmail.com",
                "name": "Submitter",
                "phoneNumber": ""
            },
            "commitId": "67888",
            "createDateTimeClient": "2017-10-02T13:38:40.521Z",
            "createDateTimeServer": "2017-10-02T13:38:40.521Z",
            "newFreightDocumentStatus": "DRAFT",
            "previousCommits": [
                "67887"
            ],
        }
    ]
}

Some of the attributes are 'read-only' and cannot be modified. For a full description see the API specs. If you want to modify this freight document, the best approach is to copy and change the result from the previous GET call. You can use the PUT /api/freightdocuments/{id} endpoint with the following body. You must keep some of the information, like roleId's of the various roles like carrier, consignor, place of taking over, etc. The 'read-only' attributes and sub-entities must be removed from the result of the GET call. The following is a valid PUT call:

PUT /api/freightdocuments/23570 HTTP/1.1
Host: partner.transfollow.com
Accept: application/json
Content-Type: application/json
Authorization: Bearer dHJ1c3RlZC1jbGllbnQ6c29tZXNlY3JldA==
Content-Length: 2684

{
    "agreedDateTimeOfTakingOver": "2017-04-11T10:36:13Z",
    "agreedDateTimeOfDelivery": "2017-04-11T10:36:13Z",
    "approvals": [],
    "attachments": [],
    "availableSigningMethods": {
        "collection": [
            "OWN",
            "TFA"
        ],
        "carrierToCarrier": [
            "OWN",
            "TFA"
        ],
        "delivery": [
            "SIGN_ON_GLASS",
            "OWN",
            "TFA"
        ]
    },
    "carrier": {
        "buildingNumber": "6",
        "cityName": "Amsterdam",
        "contactEmailAddress": "",
        "contactName": "",
        "contactNote": "",
        "contactPhoneNumber": "",
        "countryCode": "NLD",
        "countryName": "Nederland",
        "glnCode": "",
        "name": "Trucking Company",
        "postalBox": "",
        "postalCode": "3333 AA",
        "roleId": "132195",
        "streetName": "Anystreet",
        "submittedAccountEmailAddress": "carrier@example.com"
    },
    "carrierCode": "",
    "comments": [],
    "consignee": {
        "buildingNumber": "7",
        "cityName": "Amsterdam",
        "contactEmailAddress": "",
        "contactName": "",
        "contactNote": "",
        "contactPhoneNumber": "",
        "countryCode": "NLD",
        "countryName": "Nederland",
        "glnCode": "",
        "name": "Receiving Company",
        "postalBox": "",
        "postalCode": "3333 AA",
        "roleId": "132193",
        "streetName": "Anystreet",
        "submittedAccountEmailAddress": "consignee@example.com"
    },
    "consignor": {
        "buildingNumber": "7",
        "cityName": "Rotterdam",
        "contactEmailAddress": "",
        "contactName": "",
        "contactNote": "",
        "contactPhoneNumber": "",
        "countryCode": "NLD",
        "countryName": "Nederland",
        "glnCode": "",
        "name": "Sending Company",
        "postalBox": "",
        "postalCode": "1111 AA",
        "roleId": "132194",
        "streetName": "Anystreet",
        "submittedAccountEmailAddress": "consignor@example.com"
    },
    "deliveryDate": null,
    "establishedDate": "2017-12-01",
    "establishedPlace": "Anyplace",
    "estimatedDateTimeOfDelivery": "2017-12-30T00:00:00Z",
    "estimatedDateTimeOfTakingOver": "2017-12-29T00:00:00Z",
    "paymentForCarriage": "1000EUR",
    "placeOfDelivery": null,
    "placeOfTakingOver": {
        "buildingNumber": "",
        "cityName": "Rotterdam",
        "contactEmailAddress": "",
        "contactName": "",
        "contactNote": "",
        "contactPhoneNumber": "",
        "countryCode": "NLD",
        "countryName": "Nederland",
        "glnCode": "",
        "name": "Depot",
        "postalBox": "",
        "postalCode": "",
        "roleId": "132196",
        "streetName": "",
        "submittedAccountEmailAddress": ""
    },
    "references": [],
    "reimbursementAmount": 1,
    "reimbursementCurrency": "EUR",
    "senderInstructions": "None",
    "specialAgreements": "",
    "status": "DRAFT",
    "structuredGoods": [
        {
            "type": "GOOD",
            "commercialValue": null,
            "currency": null,
            "eanCode": null,
            "internalCode": null,
            "methodOfPackaging": null,
            "name": "First article",
            "natureOfGoods": null,
            "numberOfPackages": 1,
            "productId": 1,
            "shortName": null,
            "ssccCode": null,
            "statisticalNumber": 0,
            "volume": 4.5,
            "volumeUnit": "LITRE",
            "weight": 4.5,
            "weightUnit": "KILOGRAM"
        }
    ],
    "subsequentCarriers": [],
    "transportConditions": "AVC2002",
    "type": "WAYBILL"
}

Development tools

For an easy start with the TransFollow API we recommend Postman. We have assembled a Postman configuration set in a zip file. Get Postman here: https://getpostman.com