Accessing Sensibill API

Info

Currently this feature is only available for iOS. Direct native interfaces to the Sensibill APIs are not available on Android.

SDK provides the interfaces that allow a conveinent access to Sensibill API.

Overview

The main interface through which the integrator will interact with the Sensibill API Client is based on SensibillAPIProvider protocol. The protocol defines configuration and authorization for the API client, and provides the access to all Sensibill API endpoints.

An instance of the SensibillAPIProvider can be obtained in one of the following two methods:

  • Using SensibillSDK.shared.apiClient property (recommended method)
    After the SDK is started via SensibillSDK.start (see: Launching the SDK ), the Sensibill API client available via SensibillSDK.shared.apiClient property is automatically initialized with the configuration and credentials used for SDK start.
  • Standalone invocation using SensibillAPIClient.get
    If the additional customization of the configuration is required, the Sensibill API client can also be started independently, without starting the SDK. In this case the integrator needs to provide a configuration, and set the user access token delegate (to access the user-specific information). See Standalone Invocation using SensibillAPIClient.get section below for more details.

The SensibillAPIProvider allows to access the various categories of endpoints. Their naming matches the categories described in Sensibill API docs.

Currently the following categories are supported:

Additional categories will be added in the future versions.

Each category contains one or more functions, that return a SensibillAPIClient.Task , which can then be executed to send the request to the server. The task may require input models, and it has an associated output model. All input and output models are contained in SensibillAPIModel namespace.

Upon completion the task will return the SensibillAPIClient.Task itself with the result property of type Result<Model, Error>, which will contain the reuslt of the execution.

In general invocation of a Sensibill API Client endpoint involves the following steps:

  1. Obtain an instance of the SensibillAPIProvider
guard let apiClient = SensibillSDK.shared.apiClient else {
    return
}
  1. Access a category for which you’d like to send a request
let documents = apiClient.documents // returns DocumentsEndpointsProvider
  1. Create a task for a particular endpoint
do {
    let task = try documents.documents() // returns SensibillAPIClient.Task<SensibillAPIModel.GetDocumentListResponseDto>
} catch {
    // Error creating a task
}
  1. Execute the task and decide how to handle a response
task.execute { taskWithResult in

    switch taskWithResult.result {

    case .success(let model):
        // ...
    case .failure(let error):
        // ...
    case .none:
        // task completed, but no result was set
    }
}

The above code can also be converted to a shorter version:

try documents.documents().execute { taskWithResult in
    // ...
}

Details

Authorization

Almost all Sensibill API endpoints require authorization:

  • Depending on operation, endpoints in Authorization category may require client key and secret, refresh token, or JWT.
  • Most of the endpoints in the Users category require an authorization with the client token.

For these endpoints the Sensibill API Client allows to provide the authorization directly with the request. This allows the integrator control the context and the flow of requests related to authentication, and retrieving client-specific information.

The majority of the endpoints, however, access user-specific information, and require an authorization with the user access token, which they obtain via the UserAccessTokenDelegate protocol.

For Sensibill API Client available via the SensibillSDK.shared.apiClient property, the UserAccessTokenDelegate is automatically assigned to a correct user identity upon the SensibillSDK.start completion. The UserAccessTokenDelegate is unassigned with SDK is stopped.

For standalone invocation of the Sensibill API client, the caller must provide the UserAccessTokenDelegate and manage its lifecycle. See Standalone Invocation using SensibillAPIClient.get section below for more details.

  • All the endpoints that require user access token will try to obtain the current token from the assigned UserAccessTokenDelegate just before the request is started.
  • If the token is unavailable, or if a request fails with the HTTP status code 401, depending on TaskOptions (see below), the request may fail, or may call the UserAccessTokenDelegate.updateToken.

Retrying a Failed Task

In general, the SensibillAPIClient.Task.execute can be called again to retry a failed task, if desired. Before execution, the task will create a new dataTask in the underlying URLSession, and will obtain the latest user access token.

Additionally, an integrator can configure a task to retry once automatically in the following cases:

  • If the task could not obtain a current user access token
  • If the task execution failed with the status code 401 (authorization error)

This option is turned off by default.

To enable the automatic retry:

  1. Obtain an instance of the task before execution
  2. Modify the task options to enable a retry
  3. Execute the task. It will now automatically retry if needed.
let task = try apiClient.documents.documents()
task.options.shouldRefreshTokenAndRetryOnStatusCode401 = true
task.execute { ... }

Additional Information

Standalone Invocation using SensibillAPIClient.get

Info

Information in this section is not applicable when using Sensibill API Client via the SensibillSDK.shared.apiClient property.

Initialization

On standalone initialization, the SensibillAPIProvider requires:

  1. Implement the UserAccessTokenDelegate
  2. Create an instance of SensibillAPIClient.Configuration
  3. Get an instance of SensibillAPIProvider using SensibillAPIClient.get function with the configuration you created in step 2.
  4. Create an instance of UserAccessTokenDelegate you implemented in step 1, and set it for SensibillAPIProvider you created in step 3.

Note: an instance of SensibillAPIProvider created using the above steps, must be retained by an integrator. Additionally, the SensibillAPIProvider retains only the weak instance of UserAccessTokenDelegate, hence its lifecycle should be managed by a calling app.

// Step 1

class MyDelegate: UserAccessTokenDelegate {

    func currentAccessToken(for cacheIdentifier: String) -> String? {

        // ...
    }

    func updateToken(for cacheIdentifier: String, completion: @escaping (Error?) -> Void) {

        // ...
    }
}

// Step 2

let environmentUrl = "..."  // Sensibill API Environment URL
let certificatePinningOn = true // or false
let configuration = SensibillAPIClient.Configuration(
  baseUrl: environmentUrl,
  certificatePinningEnabled: certificatePinningOn
)

// Step 3

let client = SensibillAPIClient.get(configuration: configuration)

// Step 4

let delegate = MyDelegate()
let cacheIdentifier = "..." // Identify the current user
client.setUserAccessTokenDelegate(delegate, withCacheIdentifier: cacheIdentifier)

Handling User Token

When Sensibill API Client is used via the SensibillSDK.shared.apiClient property, the handling of the authorization for all user-related requests is done automatically in accordance with SDK state.

When Standalone client is instantiated, the handling of the Authorization must be handled by the caller with the help of UserAccessTokenDelegate.

  • To allow the access to user information, call SensibillAPIProvider.setUserAccessTokenDelegate. The client will ask the assigned delegate for user token for all requests that require a user token.
  • You can clear the user context any time using the SensibillAPIProvider.resetUserAccessTokenDelegate function. When UserAccessTokenDelegate is not set, any reuqests that require user token will fail.

In order to ensure that the token for a correct user is served, provide the cacheIdentifier to the client on SensibillAPIProvider.setUserAccessTokenDelegate, and compare to a supplied cacheIdentifier in your implementation of UserAccessTokenDelegate:

// UserAccessTokenDelegate implementation

class MyDelegate: UserAccessTokenDelegate {

    let cacheIdentifier: String

    init(cacheIdentifier: String) {
        self.cacheIdentifier = cacheIdentifier
    }

    func currentAccessToken(for cacheIdentifier: String) -> String? {

        guard cacheIdentifier == self.cacheIdentifier else { return nil }

        // return current token
    }

    func updateToken(for cacheIdentifier: String, completion: @escaping (Error?) -> Void) {

        guard cacheIdentifier == self.cacheIdentifier else {

            completion(/* Error indicating an incorrect user context */)
            return
        }

        // otherwise update the token
    }
}