Warranty Life Mobile SDK Last updated: 2023-09-13

1.1 Introduction

The set of functions in this SDK was originally designed to support our main mobile app, the Warranty Life Diagnostic app (iOS, Android). The flow of our partners' apps is likely to follow a similar flow, so it is helpful to review this first as the standard way to integrate this SDK. Wherever this SDK does not adequately support your use case, please contact us about the issue and we can look into supporting your situation by adding to the SDK where necessary.

1.2 App Permissions

To be able to collect enough data to assess the customer's risk profile accurately, the app must run in the background beyond the initial registration process. The exact mechanisms that the app uses to achieve this goal are different between the two platforms but they both rely on permissions that are granted by the customer to the app; for example Notification and Location permissions.

These permissions must be granted during the registration flow, and must also remain granted to the app in order for the app's background processes to be able to collect this data.

Because both platforms now include mechanisms to identify, monitor, and highlight apps' use of features that require these permissions and to actively expire permissions for apps that have not been actively used by the customer after some period of time has passed without the app being used in the foreground, it may be prudent to inform customers of why each permission is needed by the app, and, where necessary, inform them of consequences of revoking these permissions where such consequences exist. For example, Notification permission is necessary for our servers to send silent push notifications to the app and wake it up if/when necessary, and if/when a customer revokes this permission, the app may lose its ability to run in the background, will no longer collect data necessary for an accurate risk assessment of the customer, and could result in the loss of a usage/risk based reward for that customer if their risk profile can no longer be fully verified.

Example App Flow

The following demonstrates the standard use case for this SDK, which is demonstrated by our own app.

2.1 Get Voucher by Voucher Code

The [Warranty] Get Voucher by Voucher Code call is made after users enter the voucher code. This is the first step of the product registration process.

2.2 User Registration

Once the user has finished all required diagnostic tests we need to check if the user has logged in or not. If the email has been registered then we should ask the user to enter the password otherwise we should show register the register new user screen.

1. If the user has not logged in we should ask the user to enter his or her email address
2. To check if the email has registered with WarrantyLife or not, execute [Auth] Email-Registered Check API.
3. If the email is registered with WarrantyLife, then you need to ask the user to enter the password. Execute [Auth] Get Token via user password (Login) API for login via email and password.
4. If the email is not registered with WarrantyLife, users have to fill in their details to register. For the registration of new users with WarrantyLife, execute [Auth] Register New User.
5. For reset password, execute [Auth] Recover lost password API.

2.3 Create Item

The [Item] Create Item call is made after the user has entered a valid voucher code, passed all required diagnostic tests and logged in or registered as a new user. Before making the Create Item Diagnostic Report call we need to create an item on the server.

Important: IMEI Screen - sequence of API execution for create item and uploading diagnostic details.

  1. [Item] Get Item by Device ID: returns an existing item or null if the item is does not exist.
  2. [Item] Create Item: execute this API only if item does not exist in the response of Get Item by Device ID API.
  3. [Item] Create Sensor Event: returns "sensorEventId", use this "sensorEventId" to upload sensor readings and sound clip.
  4. [Item] Create Item Diagnostic Report: Add "sensorEventId" and "sensorTestId" key-value pair. It returns “id” (DiagnosticsId).
  5. [Item] Create Sensor Event: Send Event Basic Data, Upload Sensor Readings, Upload Sound (Sensor Test Audio). Use DiagnosticsId from above API.
  6. [Item] Upload Item Diagnostic Image: (Front, Rear, Colour, Reference, Test). Use DiagnosticsId from above API.
  7. (FIXME - No API call docs for this) Update Notification Token : Execute this API to upload FCM notification token to the server.

2.4 Create Item Diagnostic Report

Execute [Items] Create Item Diagnostic Report API to create diagnostic report of the device on the server. You will receive a diagnosticId in the API response, which will be used in the following steps.

2.5 Upload Diagnostic Images (Front, Rear, Colour, Reference, Test)

Using diagnosticId, upload images individually (captured and stored in the device during tests) by executing [Items] Upload Item Diagnostic Image (Front, Rear, Colour, Reference, Test) API.

2.6 Update Item (Sensor Events)

The [Item] Create Sensor Event call is used for sending all kinds of sensor events. The Case Detection Test sensor event (1111) is made after the Create Item Diagnostic Report because we need the diagnosticId for Create SensorReadings and Upload Item Diagnostic Sound calls.

2.7 Upload Sensor Readings

Using diagnosticId, upload sensor readings (collected during sensor test) by executing [Item] Create Sensor Event API.

2.8 Upload Sound File

Using diagnosticId, upload a sound file (recorded and saved in the device during sensor test) by executing [Item] Create Sensor Event API.

2.9 Create Survey

To upload user’s answers to the demographic survey, execute : Create Survey API.

2.10 Get Item by Device ID

If the user is not logged in the application, you need to execute [Item] Get Item by Device ID PUBLIC. If the user logged in or device is covered by the warranty, you need to execute [Item] Get Item by Device ID.

This api call is made in multiple places:

  • after app launch(if the user is logged in or device is covered by the warranty)
  • after the user logged in
  • registration details screen

2.11 Create Warranty

Execute [Warranty] Create Warranty by redeeming Voucher API after the user completed all required diagnostic tests. To make an update in the existing warranty, use [Warranty] Update Warranty API.

API Methods and Integration

Integration First Steps

Prerequisites

Minimum supported version is Android Oreo 8.0 (API level 26), Java 8

Getting Started

Add the sdk dependency to your app's build.gradle file. Replace "X.X.X" with the latest stable version of SDK.

dependencies {
    implementation 'com.warrantylife.sdk:X.X.X'
}
Initialization

Before using any API functionality through this SDK, the setup() function must be called once.

fun build(context: Context, clientCode: String?, sentryClientKey: String?): ApiRepository

Before using all API functions developers MUST set the default clientCode, which is a short string which will be provided to you by Warranty Life. This only needs to be set once in your code. If you don't set the client code all api functions will return "Client Code not set" error. This function returns an instance of the ‘ApiRepository’ class, facilitating the interaction with WarrantyLifeSDK APIs.

Use updateServerUrl() to change the base server URL:

const val DEFAULT_BASE_URL = "https://www.warrantylife.com/mapi/"
const val DEV01_BASE_URL = "http://dev01.warrantylife.com/app_dev.php/mapi/"
const val SANDBOX_BASE_URL = "http://sandbox.warrantylife.com/mapi/"

fun updateServerUrl(newUrl: String, clientCode: String)

Example: The following code snippet demonstrates the implementation of a Dagger Hilt dependency injection module that utilizes the @Singleton annotation in conjunction with the @Provides annotation to create a singleton instance of the ApiRepository within an Android application.

@Singleton
@Provides
fun provideApiRepository(@ApplicationContext context: Context): ApiRepository {
   return WarrantyLifeSDK.build(context, CLIENT_CODE, SENTRY_CLIENT_KEY)
}

// to update API base server
updateServerUrl(DEV01_BASE_URL, CLIENT_CODE)
Import the SDK (i.e. the Pod file in iOS)

An example of a Pod file to import the Warranty Life SDK via BitBucket is below. Note that this publication method may change in the future.

use_frameworks!

platform :ios, '13.0'

target 'YOUR PROJECT NAME' do
  pod 'WLSDKIOS', :git => 'https://account@bitbucket.org/WarrantyLife/wlsdkios.git'
end
												
Initialization:

Before using any API functionality through this SDK, the set function must be called once. developers MUST set the default clientCode, which is a short string which will be provided to you by Warranty Life. This only needs to be set once in your code. If you don't set the client code all api functions will return "Client Code not set" error.

static func set(clientCode: String, sentryAPIKey: String? = nil)

3.1 [Auth] Email-Registered Check

This call is used to determine if a given email address exists and is activated with Warranty Life. If the HTTP Code is 200 OK, then the email exists and is activated. If the code is 404, then either the email does not exist or it is not activated. In either case, a 404 indicates that a mobile app should require the user to register with Warranty Life rather than log in. Before making the call, you need to check if the entered email format is valid.

suspend fun emailRegisteredCheck(email: String): ApiResponse<ResponseBody>
Example:
CoroutineScope(Dispatchers.IO).launch {
   val apiResponse = apiRepository.emailRegisteredCheck(email)
   withContext(Dispatchers.Main) {
       when (apiResponse) {
           is ApiSuccessResponse -> {
               //navigateToPasswordScreen
           }
           is ApiErrorResponse -> {
               when(apiResponse.errorCode) {
                   404 -> {
                       // navigate to new registration screen
                   }
                   else -> {
                       // show error
                   }
               }
           }
       }
   }
}

 func emailRegisteredCheck(_ email: String, completion: @escaping (Result<Void, WLAPIError>) -> Void)
Example:
    WLNetworkManager.shared.emailRegisteredCheck("123@123.com") { result in
      // dismiss hud
      switch result {
      case .success:
        // go to entering password view controller
      case .failure:
        // go to registering new user view controller
      }
    }
{% verbatim %}GET {{api_url}}/{{api_client_code}}/{{api_version}}/auth/activated-account-check?email={{user_email}}{% endverbatim %}

3.2 [Auth] Register New User

The call registers a new user and returns information about the new registration. After successful registration the most important values returned are the identity and token properties of the JSON response. These will be used as the Basic auth username and password for all subsequent API requests.

suspend fun registerNewUser(user: WLRegisterNewUserModel): ApiResponse<LoginResponse>
Example:
val registerNewUserModel = WLRegisterNewUserModel(
   email = "123@123.com",
   password = "123",
   firstName = "123",
   lastName = "123",
   country = "US",
   ………
)

CoroutineScope(Dispatchers.IO).launch {
   val apiResponse = apiRepository.registerNewUser(user)
   withContext(Dispatchers.Main) {
       when (apiResponse) {
           is ApiSuccessResponse -> {
               apiResponse.data?.let { result ->
                   // registration successful
               }
           }

           is ApiErrorResponse -> {
               _success.value = false
               if (apiResponse.errorCode == 409) {
                   // email exists..
               }
               // show error
           }
       }
   }
}
func registerNewUser(_ model: WLRegisterNewUserModel, completion: @escaping (Result<Void, WLAPIError>) -> Void)
Example:
let registerNewUserModel = WLRegisterNewUserModel(firstName: "123", lastName: "123", password: "123", country: "US", email: "123@123.com")
WLNetworkManager.shared.registerNewUser(registerNewUserModel) { result in
  switch result {
  case .success:
	// go to entering IMEI view controller
  case .failure(let error):
	// show error popup with returned error message(error.message)
  }
}

											
{% verbatim %}POST {{api_url}}/{{api_client_code}}/{{api_version}}/auth/user{% endverbatim %}
Request
{% verbatim %}{
  "email": "{{user_email}}",
  "password": "{{user_password}}",
  "firstName": "{{user_first_name}}",
  "lastName": "{{user_last_name}}",
  "country": "{{user_country_code}}"
}
{% endverbatim %}
Response 202 - Account created
{% verbatim %}{
  "identity": "testuser001@warrantylife.com",
  "token": "RRr8yR00XF9vrkcjgnyfftv5tqsSPECDt",
  "user": {
	"acceptedMarketing": true,
	"activatedAt": "2023-02-08",
	"city": "Testville",
	"company": "TestCo, Inc.",
	"country": "United States",
	"countryCode": "US",
	"email": "testuser001@warrantylife.com",
	"firstName": "Bob",
	"languageCode": "en",
	"lastName": "Roberts",
	"phone": "3608881111",
	"state": "Washington",
	"stateCode": "WA",
	"streetAddress": "Suite 100; 1234 Test Street",
	"zip": "98101"
  }
}
{% endverbatim %}
Response 400 - Missing required fields
{% verbatim %}{
  "code": 400,
  "message": "Password Is Required,
First name is required.,
Last name is required.,
Country is required."
}
{% endverbatim %}
Response 400 - Bad country code
{% verbatim %}{
  "code": 400,
  "message": "country must be a supported 2-char country code. e.g. US, CA, AU, IN, etc."
}
{% endverbatim %}
Response 409 - User exists
{% verbatim %}{
  "code": 409,
  "message": "Specified email address already assigned to an active account; Please log them in instead."
}
{% endverbatim %}

3.3 [Auth] Get Token via user password (Login)

This request is used to log the user in with a username and password supplied by the user. The response contains an identity and token property to use as Basic Auth for all subsequent requests.

suspend fun getTokenViaUserPassword(username: String, password: String): ApiResponse<LoginResponse>
Example:
CoroutineScope(Dispatchers.IO).launch {
   val apiResponse = apiRepository.getTokenViaUserPassword(email, password)
   withContext(Dispatchers.Main) {
       when (apiResponse) {
           is ApiSuccessResponse -> {
               apiResponse.data?.let { result ->
                   // login successful
               }
           }

           is ApiErrorResponse -> {
               // show error
           }
       }
   }
}

func getTokenViaUserPassword(email: String, password: String, completion: @escaping (Result<Void, WLAPIError>) -> Void)
Example:
WLNetworkManager.shared.getTokenViaUserPassword(email: "123@123.com", password: "123") { result in
switch result {
  case .success:
	// check if dynamicLinks is enabled, if YES go to smart saver view controller, otherwise go back to home view controller
  case .failure(let error):
	// show error popup
  }
}
											
{% verbatim %}POST {{api_url}}/{{api_client_code}}/{{api_version}}/auth/token{% endverbatim %}
Request
{% verbatim %}{
  "username": "{{user_email}}",
  "password": "{{user_password}}"
}
{% endverbatim %}
Response 200 - Token returned
{% verbatim %}{
  "identity": "{{auth_id}}",
  "token": "{{auth_token}}",
  "user": {
    "acceptedMarketing": true,
    "activatedAt": "2023-02-08",
    "city": "{{user_city}}",
    "company": "{{user_company}}",
    "country": "United States",
    "countryCode": "US",
    "email": "testuser@warrantylife.com",
    "firstName": "Bob",
    "languageCode": "en",
    "lastName": "Roberts",
    "phone": "3608881111",
    "state": "Washington",
    "stateCode": "WA",
    "streetAddress": "Suite 100; 1234 Test Street",
    "zip": "98101"
  }
}
{% endverbatim %}
Response 401 - Incorrect Password
{% verbatim %}{
  "code": 401,
  "message": "Invalid Password."
}
{% endverbatim %}
Response 404 - Not found
{% verbatim %}{
  "code": 404,
  "message": "That user does not exist."
}
{% endverbatim %}

3.4 [Auth] Recover lost password

Used for the "Forgot my password" feature. The user will receive an email with instructions to change their password. No authentication required.

suspend fun recoverLostPassword(email: String): ApiResponse<ResponseBody>
Example:
CoroutineScope(Dispatchers.IO).launch {
   val apiResponse = apiRepository.recoverLostPassword(email)
   withContext(Dispatchers.Main) {
       when (apiResponse) {
           is ApiSuccessResponse -> {
               // show message: An email has been sent. Please check this to reset your password, then try logging in again.
           }
           is ApiErrorResponse -> {
               // show error
           }
       }
   }
}
func recoverLostPassword(_ email: String, completion: @escaping (Result<Void, WLAPIError>) -> Void)
Example:
WLNetworkManager.shared.recoverLostPassword("123@123.com") { result in
switch result {
  case .success:
	// show success popup
  case .failure(let error):
	// show error popup
  }
}
											
{% verbatim %}POST {{api_url}}/{{api_client_code}}/{{api_version}}/auth/recover{% endverbatim %}
Request
{% verbatim %}{
  "email": "{{user_email}}"
}
{% endverbatim %}
Response 204 - No content
{% verbatim %}{% endverbatim %}
Response 404 - Email not found
{% verbatim %}{
      "code":404,
      "message":"No account found for the given email address."
}
{% endverbatim %}
Response 429 - Request already made
{% verbatim %}{
      "code":429,
      "message":"A password-reset request has already been made recently;  Please check your email."
}
{% endverbatim %}

3.5 [Diagnostic] Get Sensor Tests

Retrieves a list of all available sensor tests.


Not required in the Android App

func getSensorTests(completion: @escaping (Result<[WLSensorTestModel], WLAPIError>) -> Void)
Example:
WLNetworkManager.shared.getSensorTests { result in
switch result {
  case .success(let tests):
	// save sensor tests
  case .failure(let error):
	// check with server again, if fails then send report to Sentry
  }
}
											
{% verbatim %}GET {{api_url}}/{{api_client_code}}/{{api_version}}/sensor-tests{% endverbatim %}

3.6 [Warranty] Get Voucher by Voucher Code

Look up a Voucher by its Voucher Code. If the code is valid this should always return 200 OK. An important property in the response is isClaimed. If this property is true then this voucher code has already been used for registration and typically cannot be used a second time. The diagnosticRequirements indicates the tests to be run with their pass/fail requirements for registration, any SmartSaver rewards/offers available and the surveys which should be displayed to the user. If the voucher has been claimed item and owner information is also returned.

suspend fun getVoucherByVoucherCode(
   voucherCode: String,
   model: String
): ApiResponse<VoucherDetail>
Example:
CoroutineScope(Dispatchers.IO).launch {
   val apiResponse = apiRepository.getVoucherByVoucherCode(code.text.formatVoucherCode(), Build.MODEL)
   withContext(Dispatchers.Main) {
       when (apiResponse) {
           is ApiSuccessResponse -> {
               apiResponse.data?.let { result ->

                   // configure required tests(including sensor test)
                   // configure case brand and models
                   // configure demographics & driving survey questions
                   // get ddd offers
                   // check SOAP(Smart saver on Annual Plans): details can be found: here
                   // SOAP example codes:


                   if(result.isClaimed == true && result.voucherItem != null) {
                       if(isSameDevice) {
                           // yes: make an update to the Item rather than create a new one
                           // check current app user and warranty owner
                       } else if(result.isDeviceTransferrable == true) {
                           // yes:  create a new Item instead of updating the existing Item
                           // check current app user and warranty owner
                       } else {
                           // show deny user alert - Sorry, this warranty can only be used on the device for which it was originally purchased.
                       }
                   }
               }
           }
           is ApiErrorResponse -> {
               when(apiResponse.errorCode) {
                   403 -> _showVoucherAlreadyClaimed
                   404 -> _showVoucherDoseNotExist
                   else -> {
                       // show error
                   }
               }
           }
       }
   }
}
func getVoucherByVoucherCode(_ code: String, completion: @escaping (Result<WLVoucherDetailsModel, WLAPIError>) -> Void)
Example:
WLNetworkManager.shared.getVoucherByVoucherCode("1234-1234-1234-1234") { result in
switch result {
  case .success:
	// configure required tests(including sensor test)
    // configure case brand and models
    // configure demographics survey questions
    // get ddd offers
    // check SOAP: MA-339, Google doc
	if appRegistrationDate.isEmpty { // code has not been registered through mobile app
  		askForPurchaseProve { isSame in
			if isSame { //  yes: make an update to the Item rather than create a new one
				self.checkCurrentAppUserAndWarrantyOwner()
			} else {
				if isDeviceTransferrable { // yes:  create a new Item instead of updating the existing Item
				self.checkCurrentAppUserAndWarrantyOwner()
			} else {
				self.showDenyUserAlert() // no: deny the user
			}
		}
  	} else { // code has already been registered through mobile app
  		// check if it's the same device
  		if isTheSameDevice(json: json["item"]) {
			warrantyHasRegisteredForThisDevice()
  		} else { // not the same device
		if isDeviceTransferrable {
	  		askForPurchaseProve { isSame in
				if isSame {
		  			self.checkCurrentAppUserAndWarrantyOwner()
				} else {
		  			self.showDenyUserAlert()
				}
	  	}
	} else {
	  showDenyUserAlert()
	}
  }
  // check if there are still unfinished or failed tests, if yes go to test screen, if no then check if user is already logged in, if not show login screen, if yes go to IMEI screen.
  case .failure(let error):
	// show error popup
  }
}
											
{% verbatim %}GET {{api_url}}/{{api_client_code}}/{{api_version}}/vouchers/{{voucher_code}}{% endverbatim %}
Response 200 - Voucher code found
{% verbatim %}{
  "requiresReceipt": false,
  "code": "TEST-4EPN-SQJ7-6HH6",
  "isClaimed": false,
  "hardRegistrationDeadlineDays": 3000,
  "softRegistrationDeadlineDays": 3000,
  "diagnosticRequirements": {
	  "bluetoothTest": 2,
	  "cellularTest": 2,
	  "wifiTest": 2,
	  "accelerometerTest": 2,
	  "gyroscopeTest": 0,
	  "vibrationTest": 0,
	  "proximityTest": 0,
	  "headsetTest": 0,
	  "buttonTest": 0,
	  "fingerprintTest": 0,
	  "microphoneTest": 0,
	  "batteryTest": 0,
	  "multitouchTest": 7,
	  "touchscreenTest": 7,
	  "glassTestCracks": 7,
	  "glassTestDiscoloration": 0,
	  "glassTestDeadPixels": 0,
	  "rearGlassTest": 0,
	  "rearMultiCamTest": 0,
	  "deadSubpixelsRed": 0,
	  "deadSubpixelsGreen": 0,
	  "deadSubpixelsBlue": 0,
	  "frontCameraTest": 0,
	  "nfcCaseTest": 0,
	  "nfcDaemon": 0,
	  "caseForm": "applied",
	  "sensorTest": {
		  "id": "1",
		  "sensorTestPhases": [
			  {
				  "id": "1",
				  "phase": 50,
				  "durationMs": 50
			  },
			  {
				  "id": "2",
				  "phase": 5000,
				  "durationMs": 500
			  },
			  {
				  "id": "3",
				  "phase": 50,
				  "durationMs": 50
			  },
			  {
				  "id": "4",
				  "phase": 5100,
				  "durationMs": 1000
			  },
			  {
				  "id": "5",
				  "phase": 50,
				  "durationMs": 50
			  },
			  {
				  "id": "6",
				  "phase": 5200,
				  "durationMs": 5000,
				  "argInt1": 1000,
				  "argInt2": 2000,
				  "argInt3": 50
			  },
			  {
				  "id": "7",
				  "phase": 50,
				  "durationMs": 50
			  },
			  {
				  "id": "8",
				  "phase": 5900,
				  "durationMs": 500
			  },
			  {
				  "id": "9",
				  "phase": 50,
				  "durationMs": 50
			  }
		  ]
	  },
	  "doReviewRequest": true,
	  "gradeFront": 0,
	  "gradeBack": 0,
	  "gradeSides": 0,
	  "lidarTest": 0,
	  "screenProtectorForm": 0,
	  "screenProtectorTest": 0,
	  "surveyVersion": 1
  },
  "verifiedCustomerPurchaseDate": "",
  "caseOptions": {
	  "brands": [
		  {
			  "id": "1916103",
			  "name": "Aardvark"
		  },
		  ...
		  ...
		  ...
	  ]
  },
  "terms_url": "https://www.warrantylife.com/plans/terms/cellairis"
}
{% endverbatim %}
Response 404 - Email not found
{% verbatim %}{
      "code":404,
      "message":"Not found"
}
{% endverbatim %}

3.7 [Item] Create Item

Create a new Item for the logged-in user. The item described the basic information about the user's device. The encoded id property in the response is used as the item_id in subsequent requests.

suspend fun createItem(item: WLCreateItemModel): ApiResponse<CreateItemResponse>

Here the mpn field should always be the device manufacturer + device model name that you get from a function call Build.MANUFACTURER and Build.MODEL. For categoryId field use const val TABLET_DEVICE_ID = 93 or const val PHONE_DEVICE_ID = 96 according to the device type.

Example:
val createItemModel = WLCreateItemModel(
   deviceId = "test",
   serialNumber = "551122662626272",
   categoryId = "96", // const val TABLET_DEVICE_ID = 93, const val PHONE_DEVICE_ID = 96
   manufacturerName = "Google",
   model = "Pixel 6",
   mpn = "Pixel 6"
)

CoroutineScope(Dispatchers.IO).launch {
   val apiResponse = apiRepository.createItem(item)
   withContext(Dispatchers.Main) {
       when (apiResponse) {
           is ApiSuccessResponse -> {
               apiResponse.data?.let { result ->
                   // make create diagnostic report call
               }
           }
           is ApiErrorResponse -> {
               // show error
           }
       }
   }
}
func createItem(completion: @escaping (Result<Void, WLAPIError>) -> Void)

Note: for iOS serialNumber field is empty, categoryId is 95 for iPhone 92 for iPad, model is the device name(e.g iPhone 14 Pro Max), mpn field should always be the model name that you get from a function call(e.g iPhone 15,1)

The id in the API response is the itemId, we need to save it locally so we can use it in other api calls.

Example:

WLNetworkManager.shared.createItem() { result in
  switch result {
  case .success:
	// make create diagnostic report call
  case .failure(let error):
	// show error popup
  }
}
											
{% verbatim %}POST {{api_url}}/{{api_client_code}}/{{api_version}}/items{% endverbatim %}
Request
{% verbatim %}{
	"deviceId": "{{item_device_id}}",
	"serialNumber":"{{item_serial}}",
	"categoryId": "{{item_category_id}}",
	"manufacturerName": "{{item_manufacturer_name}}",
	"model": "{{item_model}}",
	"mpn": "{{item_mpn}}"
}

{% endverbatim %}
Response 201 - Created
{% verbatim %}{
  "id": "{{item_id}}",
  "deviceId": "{{item_device_id}}",
  "productId": "3324097",
  "serialNumber": "{{item_serial}}",
  "categoryId": 96,
  "manufacturerName": "{{item_manufacturer_name}}",
  "model": "{{item_model}}",
  "mpn": "{{item_mpn}}"
}
{% endverbatim %}

3.8 [Item] Create Item Diagnostic Report

Submits a new Item Diagnostic Report on the previously created Item.

suspend fun createItemDiagnosticReport(
   itemID: String,
   deviceDiagnosticData: MutableMap
): ApiResponse<DiagnosticReport>
Example:
val diagnosticDetails = mutableMapOf<String, Any?>()
diagnosticDetails.put("imei", "{device_imei_number}")
…
// add all the diagnostic details in the hashmap
CoroutineScope(Dispatchers.IO).launch {
   val apiResponse = apiRepository.createItemDiagnosticReport(itemId, diagnosticDetails)
   withContext(Dispatchers.Main) {
       when (apiResponse) {
           is ApiSuccessResponse -> {
               apiResponse.data?.let { result ->
                   // upload images & send sensor test data
               }
           }
           is ApiErrorResponse -> {
               // show error
           }
       }
   }
}
func createItemDiagnosticReport(_ params: [String: Any], completion: @escaping (Result<Void, WLAPIError>) -> Void)
Example:
 // note: in iOS the resultCode is always P and the sim is always 8923440000000000003
let params: [String: Any] = ["imei": 1, "sim": "8923440000000000003", "resultCode": "P"]
WLNetworkManager.shared.createItemDiagnosticReport(params) { result in
  switch result {
  case .success:
	// upload images & send sensor test data
  case .failure(let error):
	// show error popup
  }
}
											
{% verbatim %}POST {{api_url}}/{{api_client_code}}/{{api_version}}/items/{{item_id}}/diagnostics{% endverbatim %}
Request
{% verbatim %}{
	"resultCode": "P",
	"imei": "{{item_serial}}",
	"sim": "8923440000000000003",
	"rootAccess": false,
	"bluetoothTest": true,
	"cellularTest": true,
	"wifiTest": true,
	"accelerometerTest": true,
	"gyroscopeTest": false,
	"batteryTest": true,
	"touchscreenTest": true,
	"glassTest": true,
	"glassTestCracks": true,
	"glassTestDiscoloration": true,
	"glassTestDeadPixels": true,
	"frontCameraTest": true,
	"uptime": "48:30:12",
	"mpn": "{{item_mpn}}",
	"model": "{{item_model}}",
	"brand": "{{item_manufacturer_name}}",
	"board": "Universal 5433",
	"dimension": "78x150x8mm",
	"weight": "150g",
	"released": "2016-12-2",
	"cpu": "Exynos 7740",
	"cpuCores": "32",
	"cpuArch": "Cortext A57",
	"cpuFreq": "1.2 GHz",
	"cpuMfgProcess": "14 nm",
	"cpuRevision": "r1p0",
	"cpuTemp": "29.5",
	"gpuArch": "ARM",
	"gpuRenderer": "Mali-T760",
	"gpuLoad": 10,
	"screenSize": "6.52"",
	"screenResolution": "7680 × 4320 pixels",
	"screenDensity": "3600 dpi",
	"ram": "4096 MB",
	"ramFree": "2024 MB",
	"ramUsed": "2072 MB",
	"storage": "128 GB",
	"storageUsed": "100 / 28 GB",
	"osVersion": "10.0.2",
	"apiLevel": "23",
	"kernelArch": "ar,v71",
	"kernelVersion": "3.10.9",
	"batteryCapacity": "3000 mAh",
	"batteryChargingStatus": "Charging",
	"batteryType": "Li-ion",
	"batteryVoltage": "4.58V",
	"acceleration": "0,0,0",
	"gyroscope": "0,0,0",
	"gravity": "0,0,0",
	"magnetic": "43 uT",
	"barometer": "866 hPa",
	"proximity": "8 cm",
	"cellOperator": "Ncell",
	"cellOperatorCode": "42902",
	"cellNetworkType": "HSDPA",
	"cellStrength": "- 79 dBm",
	"nfcCaseTest": true,
	"gyroscopeSensorName": "gyroscopeSensorName",
	"gyroscopeSensorVersion": "gyroscopeSensorVersion",
	"gyroscopeSensorVendor": "gyroscopeSensorVendor",
	"accelerometerSensorName": "accelerometerSensorName",
	"accelerometerSensorVersion": "accelerometerSensorVersion",
	"accelerometerSensorVendor": "accelerometerSensorVendor",
	"magneticSensorName": "magneticSensorName",
	"magneticSensorVersion": "magneticSensorVersion",
	"magneticSensorVendor": "magneticSensorVendor",
	"appVersion": "{{appVersion}}",
	"gradeFront": 10,
	"gradeBack": 9,
	"gradeSides": 8,
	"screenProtectorTypeId": 4,
	"screenProtectorTestGuess": true,
	"caseAppliedDuringDiagnostic": true,
	"caseBundlePurchased": true,
	"bundledCaseApplied": true,
	"isDeviceInsured": true
}
{% endverbatim %}

3.9 [User] Create Survey

This request is for submitting the answers to the demographic survey. The types of surveys and their details is found in the "Get Voucher by Voucher Code" request in the diagnosticRequirements.surveys property. The response is an encoded survey id. If the user skips the survey you still need to call this api but just send surveyVersion (required) and diagnosticId (optional). No authentication required.

suspend fun createSurvey(survey: HashMap<String, Object>) : ApiResponse<T>
Example:
val answers = arrayListOf<HashMap<String, Any>>()
val answer = hashMapOf<String, Any>()
answer["surveyQuestionId"] = 4
answer["chosenSurveyQuestionOptionId"] = 14
answers.add(answer)
…
// add all the question and answers details here

val survey = HashMap<String, Any>()
surveyData.put("surveyVersion", x)
surveyData.put("diagnosticId",  "ARgag3vrE1OVoF0OO-D6jaSQ");
surveyData.put("answers", answers);

coroutineScope.launch {
   when (val apiResponse = apiRepository.createSurvey(survey)) {
       is ApiSuccessResponse -> {
           // success
       }
       is ApiErrorResponse -> {
           // show error popup
       }
   }
}
func createSurvey(params: [String: Any], completion: @escaping (Result<Void, WLAPIError>) -> Void)
Example:
WLNetworkManager.shared.createSurvey(params: params) { result in
  switch result {
  case .success:
	// go to the next view controller
  case .failure(let failure):
	// show error popup
  }
}
											
{% verbatim %}POST {{api_url}}/{{api_client_code}}/{{api_version}}/items/{{item_id}}/diagnostics/{{diagnostic_id}}/survey{% endverbatim %}
Request
{% verbatim %}{
	"answers": [
	  {
		  "chosenSurveyQuestionOptionId": 14,
		  "surveyQuestionId": 4
	  },
	  {
		  "chosenSurveyQuestionOptionId": 15,
		  "surveyQuestionId": 5
	  },
	  {
		  "chosenSurveyQuestionOptionId": 15,
		  "surveyQuestionId": 6
	  }
	],
	"diagnosticId": "ARgag3vrE1OVoF0OO-D6jaSQ",
	"surveyVersion": 3
}
{% endverbatim %}
Response 201 - Created
{% verbatim %}
{
	"id": "AR8FXk9jAQ23s06LFahYP1A4"
}
{% endverbatim %}
Response 400 - Missing parameter
{% verbatim %}{
	"code": 400,
	"message": "Missing required parameter: surveyVersion"
}
{% endverbatim %}

3.10 [Item] Update Item

This api call updates existing item with new device serial number only if the voucher is already claimed & can be transferred to another device. See SOAP


Not required in the Android App

func updateItem(serialNumber: String, completion: @escaping (Result<Void, WLAPIError>) -> Void)
Example:
{% verbatim %}POST {{api_url}}/{{api_client_code}}/{{api_version}}/items/{{item_id}}{% endverbatim %}

3.11 [Item] Upload Item Diagnostic Image

Accepts images of various types taken during the registration process. Types include:

  • Front-of-device Mirror Photo
  • Rear-of-device Mirror Photo
  • Receipt Photo
  • Sensor Test Image: Reference
  • Sensor Test Image: Test
  • Sensor Test Image: Color

Upload a JPEG image of the front, back, reference, case detection test & color reference of the device for an associated with an Item Diagnostic.

const val SUBJECT_SP_SOUND = "sound"
const val SUBJECT_FRONT = "front"
const val SUBJECT_SP_REFERENCE = "reference"
const val SUBJECT_SP_TEST = "test"
const val SUBJECT_BACK = "back"
const val SUBJECT_SP_RECEIPT = "receipt"

suspend fun uploadImageForItem(
   registeredItemID: String,
   diagnosticDataRegistryID: String,
   image: MultipartBody.Part,
   subject: String
): ApiResponse<ResponseBody>

suspend fun uploadImageForReference(
   registeredItemID: String,
   diagnosticDataRegistryID: String,
   image: MultipartBody.Part,
   subject: String
): ApiResponse<ResponseBody>
Example:
val fileBody: RequestBody = file.asRequestBody("multipart/form-data".toMediaTypeOrNull())
val filePart: MultipartBody.Part = MultipartBody.Part.createFormData("file", imageFile.name, fileBody)

// can be launched in a separate asynchronous job
CoroutineScope(Dispatchers.IO).launch {
   val apiResponse = apiRepository.uploadImageForItem(itemId, diagnosticId, filePart, SUBJECT_FRONT)
   withContext(Dispatchers.Main) {
       when (apiResponse) {
           is ApiSuccessResponse -> {
               // image uploaded
           }
           is ApiErrorResponse -> {
               // show error
           }
       }
   }
}
func uploadImages(_ models: [WLImageDataModel], completion: @escaping (Result<Void, WLAPIError>) -> Void)
Example:
WLNetworkManager.shared.uploadImages() { result in
  switch result {
  case .success:
	// send sensor test data
  case .failure(let failure):
	// show error popup
  }
}

weak var delegate: WLUploadManagerDelegate?
Protocol WLUploadManager: class {
	func getCurrentUploadingProgress(order: Int?, percentage: Float?) {
  		// this returns the current uploading progress of a single image, if the value is nil then mean there is NO uploading in the background in order to call this function you need set the delegate in your class which you want to call this function to retrieve the current uploading process
	}
}
											
{% verbatim %}POST {{api_url}}/{{api_client_code}}/{{api_version}}/items/{{item_id}}/diagnostics/{{diagnostic_id}}/images/{{subject}}{% endverbatim %}

3.12 [Item] Upload Sensor Readings and Sound

Uploads the sensor readings and sound clip recorded during the sensor test

// Use this function to upload sound clip
suspend fun uploadSoundClips(
   itemID: String,
   derivativeTypeId: String,
   timeStamp: String,
   sound: MultipartBody.Part
): ApiResponse<ResponseBody>


// Use this function to upload sensor reading data
suspend fun uploadSensorReadings(
   itemID: String,
   sensorEventID: String,
   dropReadings: List<Any?>
): ApiResponse<ResponseBody>
Example:
// Implementation to upload sound clip

val fileBody: RequestBody = wavFile.asRequestBody("audio/x-wav".toMediaTypeOrNull())
val filePart: MultipartBody.Part = MultipartBody.Part.createFormData("audio", wavFile.name, fileBody)


// can be launched in a separate asynchronous job
CoroutineScope(Dispatchers.IO).launch {
   val apiResponse = apiRepository.uploadSoundClips(itemId, eventId, soundTime, filePart)
   withContext(Dispatchers.Main) {
       when (apiResponse) {
           is ApiSuccessResponse -> {
               apiResponse.data?.let { result ->
                   // if the sensor event is case detection test during registration then check if receipt is required,
                   // if yes then go to Activation Details view, if not then check if skipRegistrationForm,
                   // if yes then go to Owner Registration Details view, if not then go to Date Of Purchase view
               }
           }
           is ApiErrorResponse -> {
               // show error
           }
       }
   }
}

// Implementation to upload sensor reading
CoroutineScope(Dispatchers.IO).launch {
   val apiResponse = apiRepository.uploadSensorReadings(itemId, sensorEventId, readings)
   withContext(Dispatchers.Main) {
       when (apiResponse) {
           is ApiSuccessResponse -> {
               // if the sensor event is case detection test during registration then check if receipt is required,
               // if yes then go to Activation Details view, if not then check if skipRegistrationForm,
               // if yes then go to Owner Registration Details view, if not then go to Date Of Purchase view
           }
           is ApiErrorResponse -> {
               // show error
           }
       }
   }
}

See See [Item] Create Sensor Event for implementation

Send basic event data:
{% verbatim %}PUT {{api_url}}/{{api_client_code}}/{{api_version}}/items/{{item_id}}{% endverbatim %}
Upload sensor readings:
{% verbatim %}POST {{api_url}}/{{api_client_code}}/{{api_version}}/items/{{item_id}}/events/{{sensor_event_id}}/readings{% endverbatim %}
Upload sound:
{% verbatim %}POST: {{api_url}}/{{api_client_code}}/{{api_version}}/items/{{item_id}}/diagnostics/{{diagnostic_id}}/clips/sound/100{% endverbatim %}
Request

3.13 [Item] Create Sensor Event

This API call is to create a sensor event for different values of sensorEventType

// sensorEventType values
const val CASE_DETECTION_TEST_DURING_REGISTRATION = "1111"
const val SSR_KEEP_ENABLED = "1060"
const val SSR_ENABLED = "1000"
const val SSR_DISABLED = "1050"


const val SSD_KEEP_ENABLED = "6060"
const val SSD_ENABLED = "6010"
const val SSD_DISABLED = "6086"

const val HEARTBEAT_INSTALLED = "300"
const val HEARTBEAT_STARTED = "330"
const val HEARTBEAT_HEARTBEAT = "333"
const val HEARTBEAT_DEVICEOFF = "340"
const val HEARTBEAT_TERMINATED = "341"
const val HEARTBEAT_LOGOUT = "384"
suspend fun createSensorEvent(deviceID: String, body: MutableMap<String, Any>): ApiResponse<UpdateItemResponse>
Example:
val eventData = HashMap<String, Any>()
eventData["appVersion"] = BuildConfig.VERSION_CODE.toString()
eventData["dropJsVersion"] = dropJsVersion
eventData["platformVersion"] = Build.VERSION.RELEASE
eventData["eventAt"] = getCurrentFullDate() // "yyyy-MM-dd HH:mm:ss.SSS"
eventData["sensorEventType"] = eventType // For ex, use “1000” for SSR enabled


CoroutineScope(Dispatchers.IO).launch {
   val apiResponse = apiRepository.createSensorEvent(itemId, eventData)
   withContext(Dispatchers.Main) {
       when (apiResponse) {
           is ApiSuccessResponse -> {
               apiResponse.data?.let { result ->
                   // returns “sensorEventId“, use this field to make upload Sensor Readings call
               }
           }
           is ApiErrorResponse -> {
               // show error
           }
       }
   }
}
func createSensorEvent(_ model: WLSensorEventAPIDataModel, completion: @escaping (Result<Void, WLAPIError>) -> Void)
Example:
let event = WLSensorEvent(sensorEventType: .case_detection_during_registration, volumeSetting: 0, sensorTestId: "3")
let sensorEventModel = WLSensorEventAPIDataModel(event: event, readings: [], audio: Bundle.main.url(forResource: "2022-11-03", withExtension: "wav"))

WLNetworkManager.shared.createSensorEvent(sensorEventModel) { result in
  switch result {
  case .success:
	// check if receipt is required, if yes then go to Activation Details view, if not then check if skipRegistrationForm, if yes then go to Owner Registration Details view, if not then go to Date Of Purchase view
  case .failure(let error):
	// show error popup
  }
}
											
Send basic event data:
{% verbatim %}PUT {{api_url}}/{{api_client_code}}/{{api_version}}/items/{{item_id}}{% endverbatim %}
Upload sensor readings:
{% verbatim %}POST {{api_url}}/{{api_client_code}}/{{api_version}}/items/{{item_id}}/events/{{sensor_event_id}}/readings{% endverbatim %}
Upload sound:
{% verbatim %}POST: {{api_url}}/{{api_client_code}}/{{api_version}}/items/{{item_id}}/diagnostics/{{diagnostic_id}}/clips/sound/100{% endverbatim %}
Request
{% verbatim %}{
	  "appVersion": "186",
	  "caseHardnessType": "",
	  "causedBy": "",
	  "dropHeightCm": "",
	  "dropJsVersion": "{{dropJsVersion}}",
	  "eventAt": "{{eventAt}}",
	  "isEncased": "",
	  "note": "",
	  "platformVersion": "{{platformVersion}}",
	  "sensorEventPhonePositionType": "",
	  "sensorEventType": "1111",
	  "sensorTestId": 3.0,
	  "sound": "{{eventAt}}",
	  "surfaceHardnessType": ""
}
{% endverbatim %}
Response 202 - Accepted
{% verbatim %}{
	  "dropJs": "",
	  "dropJsVersion": "",
	  "sensorEventId": "3955"
}
{% endverbatim %}

3.14 [Item] Get Item by Device ID

Get item details based on the item's deviceId field. Device IDs are unique to the Item for a specific vendor on a specific platform. Only make this call after the user is logged in or the device is covered by a warranty already. This api returns itemId for the current user and all warranties registered with the device.

suspend fun getItemByDeviceID(deviceId: String): ApiResponse<ItemByDeviceIdResponse>
Example:
CoroutineScope(Dispatchers.IO).launch {
   val apiResponse = apiRepository.getItemByDeviceID(deviceId)
   withContext(Dispatchers.Main) {
       when (apiResponse) {
           is ApiSuccessResponse -> {
               // check if device is covered under warranty & isSsrEnabled
           }

           is ApiErrorResponse -> {
               // show error
           }
       }
   }
}

func getItemByDeviceId(completion: @escaping (Result<JSON, WLAPIError>) -> Void)
Example:
WLNetworkManager.shared.getItemByDeviceId() { result in
  switch result {
  case .success:
	// check if it’s covered & isSsrEnabled
  case .failure(let error):
	// show error popup
  }
}
											
{% verbatim %}GET {{api_url}}/{{api_client_code}}/{{api_version}}/items?deviceId={{device_id}}
{% endverbatim %}

3.15 [Item] Get Item by Device ID PUBLIC

Get item details based on the item's deviceId field. Device IDs are unique to the item for a specific vendor on a specific platform. The call will return a 404 Not Found if the user's device has not already been registered with WarrantyLife. The user can then proceed to register their device. If the call returns a 200 result code then the device has already been registered with WarrantyLife. The user should be instructed to log in with their existing account, optionally displaying the ownerEmailHint property in the JSON response. If the user is allowed to continue they may encounter errors if the warranty does not allow transferring the warranty to a different device. No authentication required.

suspend fun getItemByDeviceIdPublic(deviceId: String): ApiResponse<DeviceDetail>
Example:
CoroutineScope(Dispatchers.IO).launch {
   val apiResponse = apiRepository.getItemByDeviceIdPublic(deviceId)
   withContext(Dispatchers.Main) {
       when (apiResponse) {
           is ApiSuccessResponse -> {
               // device exists, need to save 'ownerEmailHint' returned from the call
           }

           is ApiErrorResponse -> {
               // show error
           }
       }
   }
}
func getItemByDeviceIdPublic(completion: @escaping (Result<[String: Any], WLAPIError>) -> Void)
Example:
WLNetworkManager.shared.getItemByDeviceIdPublic() { result in
  switch result {
  case .success(let dict):
	/*
	 ["isSsrEnabled": 0, "deviceId": dfkdfjkslsdfssdfsdfjsld, "ownerEmailHint": l****@war********e.com, "id": ARqr7dX3TJUtMj9rHHrPbuez, "isCovered": 0, "isSsdEnabled": 0]
	 */
	print("
Get Item by Device ID Public success: (dict)")
  case .failure(let error):
	print("
Get Item by Device ID Public failure", error)
  }
}
											
{% verbatim %}GET {{api_url}}/{{api_client_code}}/{{api_version}}/devices?deviceId={{device_id}}{% endverbatim %}

3.16 [Warranty] Create Warranty by redeeming Voucher

Activates a warranty on an existing item by redeeming a voucher code and referencing an existing diagnostic report.

suspend fun createWarranty(wlCreateWarrantyModel: WLCreateWarrantyModel): ApiResponse<Warranty>
Example:
CoroutineScope(Dispatchers.IO).launch {
   val apiResponse = apiRepository.createWarranty(wlCreateWarrantyModel)
   withContext(Dispatchers.Main) {
       when (apiResponse) {
           is ApiSuccessResponse -> {
               apiResponse.data?.run {
                   // enable DDD if SmartSaver is available & user accepted & granted all required permissions
                   // go to offers screen if ssd is enabled, else go to registration details screen
               }
           }
           is ApiErrorResponse -> {
               // show error
           }
       }
   }
}
func createWarrantyByRedeemingVoucher(params: [String: Any], completion: @escaping (Result<JSON, WLAPIError>) -> Void)
Example:
let params = ["voucherCode": "WUSW-TU4R-32BN-D3G9",
                  "cellairisReceiptId": "00000000",
                  "firstName": "firstName",
                  "lastName": "lastName",
                  "streetAddress": "address",
                  "city": "city",
                  "zip": "98045",
                  "state": "WA",
                  "country": "US",
                  "phone": "phone",
                  "dateOfPurchase": "2022-03-03"]
WLNetworkManager.shared.createWarrantyByRedeemingVoucher(params: params) { result in
  switch result {
  case .success:
	// enable DDD if yes
	// go to offers screen if ssd is enabled, else go to registration details screen
  case .failure(let error):
	print("
Create Warranty by redeeming Voucher failure", error)
  }
}

											
{% verbatim %}POST {{api_url}}/{{api_client_code}}/{{api_version}}/warranties{% endverbatim %}
Request
{% verbatim %}{
	"itemIds": ["{{item_id}}"],
	"voucherCode": "{{voucher_code}}",
	"cellairisReceiptId": "{{receipt_id}}",
	"firstName": "{{user_first_name}}",
	"lastName": "{{user_last_name}}",
	"streetAddress": "{{user_address}}",
	"city": "{{user_city}}",
	"zip": "{{user_zip}}",
	"state": "{{user_state}}",
	"country": "{{user_country_code}}",
	"phone": "{{user_phone}}",
	"dateOfPurchase": "{{warranty_purchase_date}}",
	"acceptedMarketing": true,
	"isDeviceInsured": true
}
{% endverbatim %}
Response 201 - Created
{% verbatim %}{
  "id": "ARKpPIjnnfV7YcpFKa2qf9T2",
  "itemIds": [
    "ARH3xkSbP7VIZ4jLUxvb9TAu"
  ],
  "dateOfPurchase": "2016-12-24T00:15:58Z",
  "pricePaid": 12.34,
  "voucherCode": "VCFA-FDQE-YZ64-X7AA",
  "cellairisReceiptId": "AS00012345678-20161231123456",
  "firstName": "Stephen",
  "lastName": "Chang",
  "streetAddress": "4545 Marguerite St.",
  "city": "Crazytown",
  "zip": "60701",
  "state": "WA",
  "phone": "1234567890",
  "country": "US",
  "acceptedMarketing": true
}
{% endverbatim %}

3.17 [Warranty] Get Warranty

Get item details based on the item's deviceId field. Device IDs are unique to the Item for a specific vendor on a specific platform. It is currently being used for checking rewards for specific warranty in the app.

suspend fun getWarrantyById(warrantyId: String): ApiResponse<Warranty>
Example:
CoroutineScope(Dispatchers.IO).launch {
   val apiResponse = apiRepository.getWarrantyById(warrantyId)
   withContext(Dispatchers.Main) {
       when (apiResponse) {
           is ApiSuccessResponse -> {
               apiResponse.data?.let { result ->
                  // success
               }
           }
           is ApiErrorResponse -> {
              // show error
           }
       }
   }
}
func getWarranty(warrantyId: String, completion: @escaping (Result<JSON, WLAPIError>) -> Void)
Example:
let warrantyId = "ARtxSBzW4jwA2WMRKcHpP4GX"
WLNetworkManager.shared.getWarranty(warrantyId: warrantyId) { result in
  switch result {
  case .success:
	print("
Get Warranty success")
  case .failure(let error):
	print("
Get Warranty failure", error)
  }
}
											
{% verbatim %}GET {{api_url}}/{{api_client_code}}/{{api_version}}/warranties/{{warranty_id}}{% endverbatim %}
Response 200 - Item found
{% verbatim %}{
  "id": "ARG_Rnby7KsGqpUGgZ2hgCqR",
  "deviceId": "AssignedID1234567",
  "dateOfPurchase": "2017-02-03T00:00:00Z",
  "isCovered": false,
  "productId": "3324097",
  "serialNumber": "IMEI56789012345",
  "categoryId": 96,
  "manufacturerName": "Samsung",
  "model": "Galaxy S6 32GB Black",
  "mpn": "G920F-S6"
}
{% endverbatim %}

3.18 [Warranty] Update Warranty

Only make this call if the given warranty has been registered already & can be transferred to another device. Developers just need to pass the voucher code which ties to this warranty.

suspend fun updateWarranty(wlCreateWarrantyModel: WLCreateWarrantyModel): ApiResponse<Warranty>
Example:
CoroutineScope(Dispatchers.IO).launch {
   val apiResponse = apiRepository.updateWarranty(getWLCreateWarrantyModel())
   withContext(Dispatchers.Main) {
       when (apiResponse) {
           is ApiSuccessResponse -> {
               apiResponse.data?.run {
                   // updated successfully
               }
           }
           is ApiErrorResponse -> {
               // show error
           }
       }
   }
}
func updateWarranty(completion: @escaping (Result<JSON, WLAPIError>) -> Void)
Example:
WLNetworkManager.shared.updateWarranty() { result in
  switch result {
  case .success:
	print("
Update Warranty success")
  case .failure(let error):
	print("
Update Warranty failure", error)
  }
}
											
{% verbatim %}PUT {{api_url}}/{{api_client_code}}/{{api_version}}/warranties/{{warranty_id}}
{% endverbatim %}

3.19 [Diagnostic] Get Diagnostic Configuration by Model

Get the configuration for a device based on its model. No authentication required.


Not required in the Android App

func getDiagnosticConfigByModel(model: String, completion: @escaping (Result<JSON, WLAPIError>) -> Void)
Example:
NetworkManager.shared.getDiagnosticConfigByModel(model: String) { result in
  switch result {
  case .success:
	// handle success
  case .failure(let error):
	// handle error
  }
}
											
{% verbatim %}GET: {{api_url}}/{{api_client_code}}/{{api_version}}/diagnostic-config?model=iPadMini1{% endverbatim %}
Response - 200 OK
{% verbatim %}
{
	"model": "iPad Mini 1",
	"expectedStatusCode": 200,
	"fingerprintTest": false,
	"vibrationTest": false,
	"proximityTest": false,
	"headsetTest": true
}
{% endverbatim %}

3.20 Fetch Countries & Provinces

This api returns all available countries & provinces from the server. No authentication required.

suspend fun getAppConfig(): ApiResponse<AppConfig>
Example:
CoroutineScope(Dispatchers.IO).launch {
   val apiResponse = apiRepository.getAppConfig()
   withContext(Dispatchers.Main) {
       when (apiResponse) {
           is ApiSuccessResponse -> {
               // list of countries and states
           }
           is ApiErrorResponse -> {
               // show error
           }
       }
   }
}
func fetchCountriesCities(completion: @escaping (Result<JSON, WLAPIError>) -> Void)
Example:
WLNetworkManager.shared.fetchCountriesCities() { result in
  switch result {
  case .success:
	print("
Fetch Countries Cities success")
  case .failure(let error):
	print("
Fetch Countries Cities failure", error)
  }
}
											
{% verbatim %}GET {{api_url}}/{{api_client_code}}/{{api_version}}/app/config{% endverbatim %}

3.21 [Item] Update Notification Token

Obtain the FCM registration token from Firebase Messaging, and utilize this token through the API to upload the notification token to the server.

updateFCMToken(itemId: String, deviceID: String, fcmToken: String): ApiResponse<ResponseBody>
Example:
CoroutineScope(Dispatchers.IO).launch {
   val apiResponse = apiRepository.updateFCMToken(preferenceManager.getItemID(), deviceId, fcmToken)
   withContext(Dispatchers.Main) {
       when (apiResponse) {
           is ApiSuccessResponse -> {
               // success
           }
           is ApiErrorResponse -> {
               // failure
           }
       }
   }
}

Not required in the iOS App.

4.1 SmartSaver

In order to opt-in Smart Saver we need to grant two permissions in Android: Location and Push Notification. During registration progress, after passed all required diagnostic tests & sent diagnostic report(this also include sensor test data, audio...etc) if smart saver is available we need to ask permissions, please check screenshots below:

If the user revoked permission(s) after SmartSaver is enabled, if app is running(in the background) for location & push notification permissions app will not quit so once app goes back to the foreground we need to open SmartSaver view & show missing permissions popup, so next time the user open the app we need to open the SmartSaver view & show missing permission popup. If the app is not running at all, next time the user opens the app we need to open the SmartSaver view & show missing permission popup. Note: we only want to show the missing permissions popup once every time the user revoked permissions.

Note: WarrantyLife server will send push notifications(silent, the user) & email when app goes 'missing' (the user's device doesn't send heartbeat calls or any other sensor event calls)

4.2 Auto test (Bluetooth)

Step 1: In order to perform bluetooth test, users need to grant bluetooth scan permission.

Step 2: Check If bluetooth is turned off, ask user to turn it on.

Step 3: Once bluetooth status is turned on, we will get the set of nearby bluetooth devices along with the following details:

  • Hardware address(MAC) of the remote device
  • Locally modifiable name (alias) of the remote device
  • Get the bond(paired) state of the remote device.
  • Friendly Bluetooth name of the remote device
  • Bluetooth device type of the remote device
  • Returns the supported features (UUIDs) of the remote device
  • Get the Bluetooth class of the remote device : which describes general characteristics and capabilities of a device. For example, a Bluetooth class will specify the general device type such as a phone, a computer, or headset, and whether it's capable of services such as audio or telephony.

4.3 Error Handling

const val CLIENT_CODE_NOT_FOUND = "Please set client code provided by Warranty Life"
const val SENTRY_DSN_NOT_FOUND = "Please set Sentry client key(DSN)"
const val SDK_BUILD_ERROR = "SDK is failed to build"

The class below is returned by the API in case of failure. The code for adding API error logs to Sentry is included within the SDK. Developers do not need to add Sentry logs separately.

class ApiErrorResponse<T>(val error: String, val errorMessage: String?, val errorCode: Int): ApiResponse<T>()

Network errors:

public enum WLNetworkError: String, Error {
  // common
  case noClientCodeFound = "Missing Default Client Code"
  case noDeviceIdFound = "Missing Device ID"
  case noInternet = "No Internet Connect"
  case requestTimedOut = "Request Timed Out"
  case invalidURL = "Invalid URL"
  case invalidHttpResponse = "Invalid Http Response"
  case decodingError = "Decoding Error"
  case corruptedData = "Data is Corrupted"
  case emailNotFound = "Email Not Found" // [Auth] Email-Registered Check - 404
  case invalidEmailAddress = "Invalid Email Address"  // [Auth] Email-Registered Check && [Auth] Recover lost password - 400
  case registerNewUser400 = "Given email address is invalid || Password Is Required || First name is required || Last name is required || Country must be a supported 2-char country code. e.g. US, CA, AU, IN, etc" // // 1: [Auth] Register New User - 400
  case emailAlreadyAssigned = "Specified email address already assigned to an active account; Please log them in instead." // [Auth] Register New User - 409
  case login400 = "Missing required parameter" // [Auth] Get Token via user password - 400
  case userNotExist = "That user does not exist." // [Auth] Get Token via user password - 404 (could be incorrect password)
  case noAccountFoundForGivenEmail = "No account found for the given email address." // [Auth] Recover lost password - 404
  case passwordResetMadeRecently = "A password-reset request has already been made recently;  Please check your email." // [Auth] Recover lost password - 429
  case voucherCodeNotFound = "Voucher Code Not Found" // Get Voucher by Voucher Code - 404
  case itemAlreadyCreated = "Item has already been created.  Use Lookup-Item-By-DeviceId endpoint." // Create Item - 409
  case createItem401 = "User not logged in or Logged in with different user" // Create Item - 401
  case createItemReport400 = "Invalid itemId given || Required argument is missing: resultCode,
Required argument is missing: sim" // Create Item Diagnostic Report - 400
  case itemNotFound = "Not Found" // Get Item by Device ID - 400
  case differentAccount = "This device was originally registered under a different account  (l***@w***.com).  Please log in with that account and try again." // Get Item by Device ID - 401
  case createWarranty400 = "Required parameter missing or wrong or item id not included" // Create Warranty by redeeming Voucher - 400 ("Required parameter missing: voucherCode,
Required parameter missing: firstName,
Required parameter missing: lastName,
Required parameter missing: streetAddress,
Required parameter missing: city,
Required parameter missing: zip,
Required parameter missing: state,
Required parameter missing: phone,
Required parameter missing: country, "country must be a supported 2-char country code. e.g. US, CA, AU, IN, etc.", "Voucher not found with given voucherCode", "You must include one itemId in the itemIds parameter", "Given itemId is out of range.", "Missing required argument: dateOfPurchase"")
  case voucherAlreadyRedeemed = "Voucher has already been redeemed." // Create Warranty by redeeming Voucher - 409 || Get Voucher by Voucher Code

  case noSavedItemId = "Item ID not Found"
  case noSavcedVoucherCode = "Voucher Code not Found"
  case unknown = "Unknown Http Error"
}