NAV
javascript php

Introduction

Welcome to the Canopy API! We follow standard behaviour in terms of URL's, JSON request/response bodies where applicable and standard HTTP error codes.

Requesting Your Credentials

Credentials are provided on request by Canopy to yourselves. Please speak to your account manager here to obtain the details for the environments.

Using API key

In the credentials you were sent you should have API key. Canopy expects for the API key to be included in all requests to the API in a header that looks like the following:

x-api-key: ePYgsiGbWF5aAHBj0xT9Pa1k5li0NPMD25PQXbAC

Requesting an API Token

To get API token, use this code:

const jwt = require("jsonwebtoken");
const axios = require("axios");

const canopyEndpointBaseUri = "canopy_base_uri";
const clientId = "your_client_id";
const clientSecretKey = "your_secret_key";
const canopyApiKey = "your_api_key";

// Function that generates jwt token payload using clientId
const generatePayload = () => {
  const now = Math.floor(Date.now() / 1000); // sec
  const expires = now + 60 * 60;
  const payload = {
    iss: "canopy.rent",
    scope: "request.write_only document.read_only",
    aud: `referencing-requests/client/${clientId}/token`,
    exp: expires,
    iat: now,
  };

  return payload;
};

const authenticate = async () => {
  // Generate jwt key
  const jwtKey = await jwt.sign(generatePayload(), clientSecretKey);

  // Send POST request
  return axios({
    url: `${canopyEndpointBaseUri}/referencing-requests/client/${clientId}/token`,
    method: "POST",
    headers: {
      // Add API key from the credentials into headers
      "x-api-key": canopyApiKey,
    },
    data: {
      jwtKey: jwtKey,
    },
  });
};

authenticate();
<?php
require_once 'vendor/autoload.php';

// For JWT generation we used Firebase PHP-JWT library: https://github.com/firebase/php-jwt
use \Firebase\JWT\JWT;

$clientId = "your_client_id";
$secretKey = "your_secret_key";

$now = time();

// Generate JWT payload
$payload = array(
    "iss" => "canopy.rent",
    "scope" => "request.write_only document.read_only",
    "aud" => "referencing-requests/client/$clientId/token",
    "exp" => $now + 60 * 60,
    "iat" => $now
);

// Generate JWT key using payload and secret key
$jwt = JWT::encode($payload, $secretKey);

echo $jwt, "\n";

$canopyEndpointBaseUri = "canopy_base_uri";
$apiKey = "your_api_key";

// We used Guzzle as HTTP client: https://docs.guzzlephp.org/en/stable/#
$client = new \GuzzleHttp\Client();

// Send POST request to get access token with your api key and generated JWT key
$response = $client->request(
  "POST",
  "$canopyEndpointBaseUri/referencing-requests/client/$clientId/token",
  [
    "headers" => [
      "Content-Type" => "application/json",
      "x-api-key" => $apiKey
    ],
    "json" => ["jwtKey" => $jwt]
  ]
);

echo $response->getBody(), "\n";
?>

The above command returns JSON structured like this:

{
  "success": true,
  "access_token": "Bearer eyaasd456FFGDFGdfgdfgdfgdfg7sdyfg35htjl3bhef89y4rjkbergv-KAj-dGh0xEuZftO_Utm6dugKQ",
  "expires": 1594907203
}

HTTP Request

POST /referencing-requests/client/:clientId/token

Body Parameters

Parameter Type Required Description
jwtKey string true Generated jwt key

Using API token

You need to request an API token to make calls to the Canopy API. Canopy expects for the API token to be included in all requests to the API in a header that looks like the following:

Authorization: Bearer eyaasd456FFGDFGdfgdfgdfgdfg7sdyfg35htjl3bhef89y4rjkbergv-KAj-dGh0xEuZftO_Utm6dugKQ

Refresh Secret Key

const axios = require("axios");

const canopyEndpointBaseUri = "canopy_base_uri";

axios({
  url: `${canopyEndpointBaseUri}/referencing-requests/client/refresh`,
  method: "POST",
  headers: {
    "Authorization": "authorization_token"
    "x-api-key": "api_key",
  },
  data: {
    secretKey: "new_secret_key",
  },
});
<?php
$canopyEndpointBaseUri = "canopy_base_uri";
$apiKey = "your_api_key";
$authorizationToken = "authorization_token";

$client = new \GuzzleHttp\Client();
$response = $client->request(
  "POST",
  "$canopyEndpointBaseUri/referencing-requests/client/refresh",
  [
    "headers" => [
      "Authorization" => $authorizationToken,
      "x-api-key" => $apiKey
    ],
    "json" => ["secretKey" => "new_secret_key"]
  ]
);

echo $response->getBody(), "\n";
?>

The above command returns JSON structured like this:

{
  "success": true,
  "requestId": "request_id",
  "data": {
    "secretKey": "new_secret_key"
  }
}

This endpoint may be called to change current secretKey (which is required for jwtKey generation) and get back freshly generated one. Accepts currently active secretKey in request body. After receiving successful response with new secretKey, previous secretKey becomes stale and should not be used for jwtKey generation.

HTTP Request

POST /referencing-requests/client/refresh

Body Parameters

Parameter Type Required Description
secretKey string true Currently active secret key

Prefill User's Rent Passport with Existing Data

If you already have some user information, such as identity, income and (or) rent, you can send it Canopy, so we will prefill the user's Rent Passport with provided data.

Set Identity

const axios = require("axios");

const canopyEndpointBaseUri = "canopy_base_uri";

axios({
  url: `${canopyEndpointBaseUri}/referencing-requests/client-user-data/set-identity`,
  method: "POST",
  headers: {
    "Authorization": "authorization_token"
    "x-api-key": "api_key",
  },
  data: {
    email: "example@email.com",
    firstName: "FirstName",
    middleName: "MiddleName",
    lastName: "LastName",
    dateOfBirth: "2000-03-05",
    phone: "88002553535",
    addresses: [
      {
        startDate: "2020-09-01",
        flat: "9",
        houseNumber: "113",
        street: "Street Name",
        countryCode: "GB",
        town: "London",
        postCode: "M1 1AE"
      }
    ]
  },
});
<?php
$canopyEndpointBaseUri = "canopy_base_uri";
$apiKey = "your_api_key";
$authorizationToken = "authorization_token";

$client = new \GuzzleHttp\Client();
$response = $client->request(
  "POST",
  "$canopyEndpointBaseUri/referencing-requests/client-user-data/set-identity",
  [
    "headers" => [
      "Authorization" => $authorizationToken,
      "x-api-key" => $apiKey
    ],
    "json" => [
      "email" => "example@email.com",
      "firstName" => "FirstName",
      "middleName" => "MiddleName",
      "lastName" => "LastName",
      "dateOfBirth" => "2000-03-05",
      "phone" => "88002553535",
      "addresses" => [
        [
          "startDate" => "2020-09-01",
          "flat" => "9",
          "houseNumber" => "113",
          "street" => "Street Name",
          "countryCode" => "GB",
          "town" => "London",
          "postCode" => "M1 1AE"
        ]
      ]
    ]
  ]
);

echo $response->getBody(), "\n";
?>

The above command returns JSON structured like this:

{
  "success": true,
  "requestId": "request_id",
  "data": {
    "email": "example@email.com",
    "firstName": "FirstName",
    "middleName": "MiddleName",
    "lastName": "LastName",
    "dateOfBirth": "2000-03-05",
    "phone": "88002553535",
    "addresses": [
      {
        "startDate": "2020-09-01",
        "flat": "9",
        "houseNumber": "113",
        "street": "Newton Street",
        "countryCode": "GB",
        "town": "Manchester",
        "postCode": "M1 1AE"
      }
    ]
  }
}

HTTP Request

POST /referencing-requests/client-user-data/set-identity

Body Parameters

Parameter Type Required Description
email string true
firstName string true
middleName string false
lastName string true
dateOfBirth string true Date format: YYYY-MM-DD
phone string true
addresses array true Array of addresses. See address data structure below

Address data structure

Parameter Type Required Description
startDate string true Date format: YYYY-MM-DD
flat string false
houseNumber string true if houseName is not present
houseName string true if houseNumber is not present
street string true
countryCode string true You can see list of available country codes in our apidocs repository
county string false
town string true
postCode string true
line1 string false
line2 string false
line3 string false

Set Income

const axios = require("axios");

const canopyEndpointBaseUri = "canopy_base_uri";

axios({
  url: `${canopyEndpointBaseUri}/referencing-requests/client-user-data/set-income`,
  method: "POST",
  headers: {
    "Authorization": "authorization_token"
    "x-api-key": "api_key",
  },
  data: {
    email: "example@email.com",
    data: {
      income: [
        {
          incomeSource: "EMPLOYED",
          annualSalary: 100000,
          paymentFrequency: "MONTHLY",
          employment: {
            companyName: "Company name",
            jobTitle: "Job title",
            employmentStatus: "FULL_TIME",
            employmentBasis: "CONTRACT",
            startDate: "2020-09-01",
            contractLength: "OVER_12_MONTHS"
          }
        },
        {
          annualSalary: 11110,
          incomeSource: "STUDENT",
          paymentFrequency: "MONTHLY",
          employment: {
              educationalInstitutionName: "Institution Name",
              grantsAvailability: true,
              startDate: "2019-02-01",
              course: "course",
              loans: true,
              loanStartDate: "2019-02-01",
              qualification: "qualification",
              courseStart: "2019-02-01",
              courseEnd: "2030-01-01"
          }
        }
      ]
    }
  },
});
<?php
$canopyEndpointBaseUri = "canopy_base_uri";
$apiKey = "your_api_key";
$authorizationToken = "authorization_token";

$client = new \GuzzleHttp\Client();
$response = $client->request(
  "POST",
  "$canopyEndpointBaseUri/referencing-requests/client-user-data/set-income",
  [
    "headers" => [
      "Authorization" => $authorizationToken,
      "x-api-key" => $apiKey
    ],
    "json" => [
      "email" => "example@email.com",
      "data" => [
        "income" => [
          [
            "incomeSource" => "EMPLOYED",
            "annualSalary" => 100000,
            "paymentFrequency" => "MONTHLY",
            "employment" => [
              "companyName" => "Company name",
              "jobTitle" => "Job title",
              "employmentStatus" => "FULL_TIME",
              "employmentBasis" => "CONTRACT",
              "startDate" => "2020-09-01",
              "contractLength" => "OVER_12_MONTHS"
            ]
          ],
          [
            "incomeSource" => "STUDENT",
            "annualSalary" => 11110,
            "paymentFrequency" => "MONTHLY",
            "employment" => [
              "educationalInstitutionName" => "Institution Name",
              "grantsAvailability" => true,
              "startDate" => "2019-02-01",
              "course" => "course",
              "loans" => true,
              "loanStartDate" => "2019-02-01",
              "qualification" => "qualification",
              "courseStart" => "2020-09-01",
              "courseEnd" => "2030-01-01"
            ]
          ]
        ]
      ]
    ]
  ]
);

echo $response->getBody(), "\n";
?>

The above command returns JSON structured like this:

{
  "success": true,
  "requestId": "request_id",
  "data": {
    "clientId": "clientId",
    "email": "example@email.com",
    "income": [
      {
        "incomeSource": "EMPLOYED",
        "annualSalary": 100000,
        "paymentFrequency": "MONTHLY",
        "employment": {
          "companyName": "Comapny name",
          "jobTitle": "Job title",
          "employmentStatus": "FULL_TIME",
          "employmentBasis": "CONTRACT",
          "startDate": "2020-09-01",
          "contractLength": "OVER_12_MONTHS"
        }
      },
      {
        "incomeSource": "STUDENT",
        "annualSalary": 11110,
        "paymentFrequency": "MONTHLY",
        "employment": {
          "educationalInstitutionName": "Institution Name",
          "grantsAvailability": true,
          "startDate": "2019-02-01",
          "course": "course",
          "loans": true,
          "loanStartDate": "2019-02-01",
          "qualification": "qualification",
          "courseStart": "2019-02-01",
          "courseEnd": "2030-01-01"
        }
      }
    ]
  }
}

HTTP Request

POST /referencing-requests/client-user-data/set-income

Body Parameters

Parameter Type Required Description
email string true
data object true Object with income field

data field structure:

Parameter Type Required Description
income array true Array on income objects. Must contain minimum 1 item. See list of possible income objects below

Possible income objects

"EMPLOYED" type:

Parameter Type Required Description
incomeSource "EMPLOYED" true Income source field with value "EMPLOYED"
annualSalary integer true The annual salary amount, which should be greater than 100 pounds.
paymentFrequency string true One of ["MONTHLY", "TWO_WEEKLY", "WEEKLY"]
additionalInfo string false
employment object true Object with employment data. See employment field structure below

Employment object structure for income source with "EMPLOYED" type:

Parameter Type Required Description
companyName string true
jobTitle string true
employmentStatus string true One of ["FULL_TIME", "PART_TIME"]
employmentBasis string true One of ["PERMANENT", "CONTRACT"]
startDate string true Date format: YYYY-MM-DD
contractLength string true if employmentBasis is CONTRACT One of ["UNDER_1_MONTH", "MONTHS_2", "MONTHS_3", "MONTHS_4", "MONTHS_5", "MONTHS_6", "MONTHS_7", "MONTHS_8", "MONTHS_9", "MONTHS_10", "MONTHS_11", "MONTHS_12", "OVER_12_MONTHS"]

"SELF_EMPLOYED" type:

Parameter Type Required Description
incomeSource "SELF_EMPLOYED" true Income source field with value "SELF_EMPLOYED"
annualSalary integer true The annual salary amount, which should be greater than 100 pounds.
paymentFrequency string true One of ["MONTHLY", "TWO_WEEKLY", "WEEKLY"]
additionalInfo string false
employment object true Object with employment data. See employment field structure below

Employment object structure for income source with "SELF_EMPLOYED" type:

Parameter Type Required Description
incomeName string true
jobTitle string true
startDate string true Date format: YYYY-MM-DD

"STUDENT" type:

Parameter Type Required Description
incomeSource "STUDENT" true Income source field with value "STUDENT"
annualSalary integer true The annual salary amount, which should be greater than 100 pounds.
paymentFrequency string true One of ["MONTHLY", "TWO_WEEKLY", "WEEKLY", "OTHER"]
additionalInfo string false
employment object true Object with employment data. See employment field structure below

Employment object structure for income source with "STUDENT" type:

Parameter Type Required Description
educationalInstitutionName string true
grantsAvailability boolean true
startDate string required if grantsAvailability is true Date format: YYYY-MM-DD
loans boolean true
loanStartDate string required if loans is true Date format: YYYY-MM-DD
course string true
qualification string true
courseStart string true Date format: YYYY-MM-DD
courseEnd string true Date format: YYYY-MM-DD

"RETIRED", "UNEMPLOYED", "BENEFITS" or "OTHER" type

Parameter Type Required Description
incomeSource string true One of ["RETIRED", "UNEMPLOYED", "BENEFITS", "OTHER"]
annualSalary integer true The annual salary amount, which should be greater than 100 pounds.
paymentFrequency string true One of ["MONTHLY", "TWO_WEEKLY", "WEEKLY", "OTHER"]
additionalInfo string false
employment object true Object with employment data. See employment field structure below

Employment object structure for income source with "RETIRED", "UNEMPLOYED", "BENEFITS" or "OTHER" type:

Parameter Type Required Description
incomeName string true
description string true
startDate string true Date format: YYYY-MM-DD

Set Rent

const axios = require("axios");

const canopyEndpointBaseUri = "canopy_base_uri";

axios({
  url: `${canopyEndpointBaseUri}/referencing-requests/client-user-data/set-rent`,
  method: "POST",
  headers: {
    "Authorization": "authorization_token"
    "x-api-key": "api_key",
  },
  data: {
    email: "example@email.com",
    data: {
      homeowner: false,
      rentsDuringLastYear: true,
      rents: [
        {
          name: "Rent name",
          paymentFrequency: "MONTHLY",
          rentPaymentAmount: 10000,
          rentPaidTo: "Paid to"
        }
      ]
    }
  },
});
<?php
$canopyEndpointBaseUri = "canopy_base_uri";
$apiKey = "your_api_key";
$authorizationToken = "authorization_token";

$client = new \GuzzleHttp\Client();
$response = $client->request(
  "POST",
  "$canopyEndpointBaseUri/referencing-requests/client-user-data/set-rent",
  [
    "headers" => [
      "Authorization" => $authorizationToken,
      "x-api-key" => $apiKey
    ],
    "json" => [
      "email" => "example@email.com",
      "data" => [
        "homeowner" => false,
        "rentsDuringLastYear" => true,
        "rents" => [
          [
            "name" => "Rent name",
            "paymentFrequency" => "MONTHLY",
            "rentPaymentAmount" => 10000,
            "rentPaidTo" => "Paid to"
          ]
        ]
      ]
    ]
  ]
);

echo $response->getBody(), "\n";
?>

The above command returns JSON structured like this:

{
  "success": true,
  "requestId": "request_id",
  "data": {
    "clientId": "clientId",
    "email": "example@email.com",
    "rentData": {
      "homeowner": false,
      "rentsDuringLastYear": true,
      "rents": [
        {
          "name": "Rent name",
          "paymentFrequency": "MONTHLY",
          "rentPaymentAmount": 10000,
          "rentPaidTo": "Paid to"
        }
      ]
    }
  }
}

HTTP Request

POST /referencing-requests/client-user-data/set-rent

Body Parameters

Parameter Type Required Description
email string true
data object true Object with rent data

data field structure

Parameter Type Required Description
homeowner boolean true
rentsDuringLastYear boolean true Object with rent data
rents array true if rentsDuringLastYear is true Array of rents during last year. Minimum 1 item

Rents item structure

Parameter Type Required Description
name string true
paymentFrequency string true One of ["MONTHLY", "TWO_WEEKLY", "WEEKLY", "OTHER"]
rentPaymentAmount integer true
rentPaidTo string true

Referencing Endpoints

Inside the Canopy system, there are 3 main entities: company, branch and agent. All communications between the agents and renters happen on the branch level, not on the company level. It means that each company must have at least one branch (let’s call it default), so all existing or new agents should belong to the particular branch(es) in order to work with the potential or actual renters. For example, if the agent belongs to Branch 1 and this agent sends the invite to the potential renter, actually the invite is sent from Branch 1, not directly from the agent or Company.

Therefore, in order to work with us, we create a Company and the default Branch for you at the very beginning. On the basic level, it’s enough for the correct workflow. There are the following possible scenarios:

Get the List of Branches and Connections

const axios = require("axios");

const canopyEndpointBaseUri = "canopy_base_uri";
const clientId = "client_id";

axios({
  url: `${canopyEndpointBaseUri}/referencing-requests/client/${clientId}/branches-list`,
  method: "GET",
  headers: {
    "Authorization": "authorization_token"
    "x-api-key": "api_key",
  },
});
<?php
$canopyEndpointBaseUri = "canopy_base_uri";
$apiKey = "your_api_key";
$clientId = "your_client_id";
$authorizationToken = "authorization_token";

$client = new \GuzzleHttp\Client();
$response = $client->request(
  "GET",
  "$canopyEndpointBaseUri/referencing-requests/client/$clientId/branches-list",
  [
    "headers" => [
      "Authorization" => $authorizationToken,
      "x-api-key" => $apiKey
    ]
  ]
);

echo $response->getBody(), "\n";
?>

The above command returns JSON structured like this:

{
  "success": true,
  "requestId": "request_id",
  "data": {
    "branches": [
      {
        "canopyBranchId": "592954da-4ea7-426b-ace3-f61dbc169ea4",
        "clientBranchId": "592954da-4ea7-426b-ace3-f61dbc169ea4",
        "branchName": "Branch 1",
        "branchAddress": {
          "id": "af380cca-e2a6-4cc2-8abc-99889f4e787f",
          "line1": "Apartment 9",
          "line2": "113 Street name",
          "line3": null,
          "street": "Street name",
          "town": "London",
          "postCode": "M1 1AE",
          "countryCode": "GB",
          "flat": "9",
          "district": null,
          "houseNumber": "113",
          "houseName": null
        }
      },
      {
        "canopyBranchId": "592954da-4ea7-426b-ace3-f61dbc169ea4",
        "clientBranchId": "0830f800-b5d2-4e16-aede-411cb5f3c58b",
        "branchName": "Branch 2",
        "branchAddress": {
          "id": "af380cca-e2a6-4cc2-8abc-99889f4e787f",
          "line1": "Apartment 9",
          "line2": "113 Street name",
          "line3": null,
          "street": "Street name",
          "town": "London",
          "postCode": "M1 1AE",
          "countryCode": "GB",
          "flat": "9",
          "district": null,
          "houseNumber": "113",
          "houseName": null
        }
      },
      {
        "canopyBranchId": "818e8ad7-97aa-460d-a007-6b65830ace5b",
        "clientBranchId": null,
        "branchName": "Branch 3",
        "branchAddress": {
          "id": "af380cca-e2a6-4cc2-8abc-99889f4e787f",
          "line1": "Apartment 9",
          "line2": "113 Street name",
          "line3": null,
          "street": "Street name",
          "town": "London",
          "postCode": "M1 1AE",
          "countryCode": "GB",
          "flat": "9",
          "district": null,
          "houseNumber": "113",
          "houseName": null
        }
      }
    ]
  }
}

The endpoint below returns the list of canopy branches associated with clientId and connections with client branches. Canopy branch might be linked with some client branch, in this case clientBranchId value will contain id of the client branch. If canopy branch linked with multiple client branches, then same canopy branch will appear in the list multiple times with different clientBranchId. If canopy branch is not linked to any client branch, then clientBranchId will be equal to null.

HTTP Request

GET /referencing-requests/client/:clientId/branches-list

URL Parameters

Parameter Description
clientId Your client reference

Response Structure

Parameter Type Description
success bool Request status
requestId string Request id
data object Object that contains branches field with list of branches

Data object structure:

Parameter Type Description
branches array List of branches

Single branch data structure:

Parameter Type Description
canopyBranchId string id of the branch in the Canopy system
clientBranchId string or null id of the client's branch that is linked with this canopy branch, if equals null than this canopy branch is not linked with any client branch branch
branchName string The name of the Canopy branch
branchAddress object Object that contains information about branch address. You can see example of the address structure in the code example
const axios = require("axios");

const canopyEndpointBaseUri = "canopy_base_uri";
const clientId = "client_id";

axios({
  url: `${canopyEndpointBaseUri}/referencing-requests/client/${clientId}/link-branch`,
  method: "POST",
  headers: {
    "Authorization": "authorization_token"
    "x-api-key": "api_key",
  },
  data: {
    canopyBranchId: "592954da-4ea7-426b-ace3-f61dbc169ea4",
    clientBranchId: "592954da-4ea7-426b-ace3-f61dbc169ea4"
  },
});
<?php
$canopyEndpointBaseUri = "canopy_base_uri";
$apiKey = "your_api_key";
$authorizationToken = "authorization_token";
$clientId = "your_client_id";

$client = new \GuzzleHttp\Client();
$response = $client->request(
  "POST",
  "$canopyEndpointBaseUri/referencing-requests/client/$clientId/link-branch",
  [
    "headers" => [
      "Authorization" => $authorizationToken,
      "x-api-key" => $apiKey
    ],
    "json" => [
      "canopyBranchId" => "592954da-4ea7-426b-ace3-f61dbc169ea4",
      "clientBranchId" => "592954da-4ea7-426b-ace3-f61dbc169ea4"
    ]
  ]
);

echo $response->getBody(), "\n";
?>

The above command returns JSON structured like this:

{
  "success": true,
  "requestId": "request_id",
  "data": {
    "canopyBranchId": "592954da-4ea7-426b-ace3-f61dbc169ea4",
    "clientBranchId": "592954da-4ea7-426b-ace3-f61dbc169ea4",
    "branchName": "Branch 1",
    "branchAddress": {
      "id": "af380cca-e2a6-4cc2-8abc-99889f4e787f",
      "line1": "Apartment 9",
      "line2": "113 Street name",
      "line3": null,
      "street": "Street name",
      "town": "London",
      "postCode": "M1 1AE",
      "countryCode": "GB",
      "flat": "9",
      "district": null,
      "houseNumber": "113",
      "houseName": null
    }
  }
}

Thish endpoint links existing Canopy branch with client's branch. This operation is required for referencing request. clientBranchId should be unique, so there can be only one connection with specific client branch. canopyBranchId can be used in multiple connections, so there can be multiple connections between single canopy branch and multiple client's branches.

HTTP Request

POST /referencing-requests/client/:clientId/link-branch

URL Parameters

Parameter Description
clientId Your client reference

Body Parameters

Parameter Type Description
canopyBranchId string id of the Canopy branch, you can get list of possible branches using GET /branches-list endpoint
clientBranchId string id of the branch in the client's system, it can be any string of uuid format and should be unique

Delete Branch Mapping

const axios = require("axios");

const canopyEndpointBaseUri = "canopy_base_uri";
const clientId = "client_id";
const clientBranchId = "client_branch_id";

axios({
  url: `${canopyEndpointBaseUri}/referencing-requests/client/${clientId}/branch-mapping/${clientBranchId}`,
  method: "DELETE",
  headers: {
    "Authorization": "authorization_token"
    "x-api-key": "api_key",
  }
});
<?php
$canopyEndpointBaseUri = "canopy_base_uri";
$apiKey = "your_api_key";
$authorizationToken = "authorization_token";
$clientId = "your_client_id";
$clientBranchId = "client_branch_id";

$client = new \GuzzleHttp\Client();
$response = $client->request(
  "DELETE",
  "$canopyEndpointBaseUri/referencing-requests/client/$clientId/branch-mapping/$clientBranchId",
  [
    "headers" => [
      "Authorization" => $authorizationToken,
      "x-api-key" => $apiKey
    ]
  ]
);

echo $response->getBody(), "\n";
?>

The above command returns JSON structured like this:

{
  "success": true,
  "requestId": "request_id",
  "data": {
    "clientBranchId": "592954da-4ea7-426b-ace3-f61dbc169ea4"
  }
}

This endpoint deletes existing branch mapping between Canopy branch and client branch.

HTTP Request

DELETE /referencing-requests/client/:clientId/branch-mapping/:clientBranchId

URL Parameters

Parameter Description
clientId Your client reference
clientBranchId id of the branch linked with the Canopy branch

Request Referencing

const axios = require("axios");

const canopyEndpointBaseUri = "canopy_base_uri";
const clientId = "client_id";

axios({
  url: `${canopyEndpointBaseUri}/referencing-requests/client/${clientId}/request`,
  method: "POST",
  headers: {
    "Authorization": "authorization_token"
    "x-api-key": "api_key",
  },
  data: {
    email: "test@email.com",
    firstName: "First name",
    lastName: "Last name",
    callbackUrl: "https://callbackurl.com",
    requestType: "RENTER_SCREENING",
    itemType: "FULL",
    title: "Title",
    phone: "88002553535",
    branchId: "branch_id",
    clientReferenceId: "12346",
    isIdentityVerificationNeeded: true,
    isRightToRentNeeded: true
  },
});
<?php
$canopyEndpointBaseUri = "canopy_base_uri";
$apiKey = "your_api_key";
$authorizationToken = "authorization_token";
$clientId = "your_client_id";

$client = new \GuzzleHttp\Client();
$response = $client->request(
  "POST",
  "$canopyEndpointBaseUri/referencing-requests/client/$clientId/request",
  [
    "headers" => [
      "Authorization" => $authorizationToken,
      "x-api-key" => $apiKey
    ],
    "json" => [
      "email" => "test@email.com",
      "firstName" => "First name",
      "lastName" => "Last name",
      "callbackUrl" => "https://callbackurl.com",
      "requestType" => "RENTER_SCREENING",
      "itemType" => "FULL",
      "title" => "Title",
      "phone" => "88002553535",
      "branchId" => "branch_id",
      "clientReferenceId" => "12346"
      "isIdentityVerificationNeeded" => true
      "isRightToRentNeeded" => true
    ]
  ]
);

echo $response->getBody(), "\n";
?>

The above command returns JSON structured like this:

{
  "success": true,
  "requestId": "request_id",
  "canopyReferenceId": "canopy_reference_id"
}

This endpoint creates new referencing request.

HTTP Request

POST /referencing-requests/client/:clientId/request

URL Parameters

Parameter Description
clientId Your client reference

Body Parameters

Parameter Type Required Description
email string true
firstName string false
lastName string false
callbackUrl string true URL to which Canopy will send PDF Report
requestType string true One of ["RENTER_SCREENING", "GUARANTOR_SCREENING"]
itemType string true One of ["PRE_QUALIFICATION", "CC_ONLY", "INSTANT", "FULL", "PRIORITY"]
title string false Title that is used before a surname or full name
phone string false
branchId string false clientBranchId that is connected to any canopy branch, you can get list of created connections using GET /branches-list endpoint, if there's no connection created with such branchId then this API call will return 404 error, new connection can be created using POST /link-branch endpoint, if no branchId is passed than default branch will be used for the referencing request
clientReferenceId string false A unique identifier on the client's side
isGuarantorNeeded boolean false Flag allowing to request a guarantor for a specified user (user email in request). Applicable only if requestType = "RENTER_SCREENING"
isIdentityVerificationNeeded boolean false Flag allowing to request an identity verification for a specified user (user email in request). Applicable only if requestType = "RENTER_SCREENING" and itemType is one of ["CC_ONLY", "INSTANT", "FULL", "PRIORITY"]
isRightToRentNeeded boolean false Flag allowing to request a right to rent verification for a specified user (user email in request). Applicable only if requestType = "RENTER_SCREENING" and itemType is one of ["CC_ONLY", "INSTANT", "FULL", "PRIORITY"]. Should also have isIdentityVerificationNeeded set to "true", but will enable it if it isn't set.

Rent Passport Retrieval

const axios = require("axios");

const canopyEndpointBaseUri = "canopy_base_uri";
const clientId = "client_id";
const documentId = "document_id";

axios({
  url: `${canopyEndpointBaseUri}/referencing-requests/client/${clientId}/documents/${documentId}`,
  method: "GET",
  headers: {
    "Authorization": "authorization_token"
    "x-api-key": "api_key",
  },
});
<?php
$canopyEndpointBaseUri = "canopy_base_uri";
$apiKey = "your_api_key";
$authorizationToken = "authorization_token";
$clientId = "your_client_id";
$documentId = "document_id";

$client = new \GuzzleHttp\Client();
$response = $client->request(
  "GET",
  "$canopyEndpointBaseUri/referencing-requests/client/$clientId/documents/$documentId",
  [
    "headers" => [
      "Authorization" => $authorizationToken,
      "x-api-key" => $apiKey
    ]
  ]
);

echo $response->getBody(), "\n";
?>

Once referencing has been completed by the renter in the Canopy mobile application, an update message will be sent to the callbackUrl provided at the moment of referencing request. This message will have the following structure:

Parameter Type Description
clientReferenceId string
canopyReferenceId string
document object Object with document data. See document object structure below

Document data structure:

Parameter Type Description
documentType integer One of [0, 1]. 0 means INSTANT screening type, 1 means FULL screening type.
url string /referencing-requests/client/:clientId/documents/:documentId
summaryReportUrl string /referencing-requests/client/:clientId/documents/:summaryDocumentId
maxRent integer
status string One of [VERIFY, CONSIDER, HIGH_RISK]
title string One of [INSTANT, FULL]
isGuarantorReference boolean Exists if requestType of reference request was GUARANTOR_SCREENING
isGuarantorNeeded boolean Exists if requestType of reference request was RENTER_SCREENING
waitingForGuarantor boolean Exists if isGuarantorNeeded has true value
guarantorCompleteRP boolean Exists if isGuarantorNeeded has true value

To retrieve a PDF Rent Passport after successful referencing completion, you can use the url field from the above response:

HTTP Request

GET /referencing-requests/client/:clientId/documents/:documentId

URL Parameters

Parameter Description
clientId Your client reference
documentId The ID of the document returned from the document update

The PDF will be included in the response body with the filename specified in the header as follows:

content-type: application/pdf
content-disposition: attachment; filename='9e6222ee-312b-2297-a03a-07700363a6b6_FULL.pdf'

Get Intermediate PDF Report

const axios = require("axios");

const canopyEndpointBaseUri = "canopy_base_uri";
const clientId = "client_id";
const canopyReferenceId = "canopy_reference_id";

axios({
  url: `${canopyEndpointBaseUri}/referencing-requests/client/${clientId}/rent-passport/${canopyReferenceId}`,
  method: "GET",
  headers: {
    Authorization: "authorization_token",
    "x-api-key": "api_key",
  },
});
<?php
$canopyEndpointBaseUri = "canopy_base_uri";
$apiKey = "your_api_key";
$authorizationToken = "authorization_token";
$clientId = "your_client_id";
$canopyReferenceId = "canopy_reference_id";

$client = new \GuzzleHttp\Client();
$response = $client->request(
  "GET",
  "$canopyEndpointBaseUri/referencing-requests/client/$clientId/rent-passport/$canopyReferenceId",
  [
    "headers" => [
      "Authorization" => $authorizationToken,
      "x-api-key" => $apiKey
    ]
  ]
);

echo $response->getBody(), "\n";
?>

The JSON format request returns JSON structured like this:

{
  "success": true,
  "requestId": "request_id",
  "data": {
    "rentPassport": {
      "statuses": {
        "overall": {
          "ready": true,
          "status": "NOT_STARTED"
        },
        "instant": {
          "income": null,
          "savings": null,
          "creditCheck": "NOT_STARTED",
          "rent": null,
          "overall": "NOT_STARTED"
        },
        "full": {
          "overall": "NOT_STARTED",
          "landlordReference": "NOT_STARTED",
          "employerReference": "NOT_STARTED"
        }
      },
      "monthlyRentAffordability": "0",
      "incomeMonthlyRentAffordability": "0",
      "savingsMonthlyRentAffordability": 0,
      "user": {
        "identity": {
          "status": "NO",
          "data": {}
        },
        "addressHistory": {
          "status": "NO",
          "data": []
        },
        "creditIndicator": "NO",
        "financialSummary": {},
        "creditScore": null,
        "dateChecked": null
      },
      "highRiskSummary": null,
      "considerSummary": {
        "ID_VERIFICATION": "NO_PHOTO_ID"
      },
      "canRequestGuarantor": true,
      "guarantorRequest": null,
      "notes": {
        "GLOBAL": {
          "EXTERNAL": {
            "message": null,
            "createdAt": null
          }
        },
        "IDENTITY": {
          "EXTERNAL": {
            "message": null,
            "createdAt": null
          }
        },
        "INCOME": {
          "EXTERNAL": {
            "message": null,
            "createdAt": null
          }
        },
        "SAVINGS": {
          "EXTERNAL": {
            "message": null,
            "createdAt": null
          }
        },
        "RENT": {
          "EXTERNAL": {
            "message": null,
            "createdAt": null
          }
        },
        "LANDLORD_REFERENCE": {
          "EXTERNAL": {
            "message": null,
            "createdAt": null
          }
        },
        "EMPLOYER_REFERENCE": {
          "EXTERNAL": {
            "message": null,
            "createdAt": null
          }
        },
        "RENT_TRACKING": {
          "EXTERNAL": {
            "message": null,
            "createdAt": null
          }
        }
      },
      "isGuarantor": false,
      "isFavourite": false,
      "connection": {
        "id": "connectionId",
        "branchId": "branchId",
        "tenancyStatus": "SCREENING",
        "branchName": "branchName",
        "renterId": "renterId",
        "referencingType": "INSTANT",
        "createdAt": "2023-09-08T18:30:22.310Z",
        "moveFrom": null,
        "moveTo": null,
        "groupStates": {
          "full": "NOT_STARTED",
          "other": "NOT_STARTED",
          "instant": "IN_PROGRESS"
        },
        "srOverall": "IN_PROGRESS",
        "isRPCompleted": false,
        "source": "REFERENCE_API"
      }
    },
    "guarantors": {
      "pending": 0,
      "accepted": 0
    },
    "guaranteedRenters": {
      "count": 0,
      "items": []
    },
    "identityVerification": {
      "status": "NO_PHOTO_ID",
      "additionalInfo": "additionalInfo",
      "updatedAt": "2023-09-08T18:30:58.021Z"
    },
    "rightToRent": {
      "data": {
        "country": null,
        "metadata": {
          "typeOfProof": "other",
          "idnowIdentID": null,
          "rtrSharecode": "Share Code"
        },
        "documents": [],
        "verifiedBy": null,
        "documentType": "other",
        "expirationDate": null,
        "tenancyEndDate": null
      },
      "type": "MANUAL",
      "status": "READY_TO_COMPLETE",
      "requestId": "request_id"
    }
  }
}

Get Screening Results of the renter, even if Passport is not completed. When you receive notifications that some of the Renter Passports sections were updated, you can call and get a PDF Report with current state. For example it is useful when renter should provide FULL referencing, but you wish to see INSTANT screening results as soon as it is completed and not wait while full screening will be passed. If requestType of last reference request for user is GUARANTOR_SCREENING, the Report will be generated as for the Guarantor.

HTTP Request

GET /referencing-requests/client/:clientId/rent-passport/:canopyReferenceId

URL Parameters

Parameter Description
clientId Your client reference
canopyReferenceId The id of registered request on the Canopy side

Also, this endpoint may accept:

Get Rent Passport Notes

const axios = require("axios");

const canopyEndpointBaseUri = "canopy_base_uri";
const clientId = "client_id";
const canopyReferenceId = "canopy_reference_id";

axios({
  url: `${canopyEndpointBaseUri}/referencing-requests/client/${clientId}/rent-passport/${canopyReferenceId}/notes`,
  method: "GET",
  headers: {
    Authorization: "authorization_token",
    "x-api-key": "api_key",
  },
});
<?php
$canopyEndpointBaseUri = "canopy_base_uri";
$apiKey = "your_api_key";
$authorizationToken = "authorization_token";
$clientId = "your_client_id";
$canopyReferenceId = "canopy_reference_id";

$client = new \GuzzleHttp\Client();
$response = $client->request(
  "GET",
  "$canopyEndpointBaseUri/referencing-requests/client/$clientId/rent-passport/$canopyReferenceId/notes",
  [
    "headers" => [
      "Authorization" => $authorizationToken,
      "x-api-key" => $apiKey
    ]
  ]
);

echo $response->getBody(), "\n";
?>

The above command returns JSON structured like this:

{
  "userDetails": {
    "email": "ddd@gmail.com",
    "firstName": "firstName",
    "lastName": "lastName"
  },
  "sections": {
    "SUMMARY": {
      "agentNotes": [
        {
          "message": "agent note",
          "createdAt": "2022-05-05T08:45:31.200Z"
        }
      ],
      "reportNotes": [
        {
          "message": "report note",
          "createdAt": "2021-01-04T08:45:31.200Z"
        }
      ]
    },
    "CREDIT_CHECK": {
      "agentNotes": [],
      "reportNotes": []
    },
    "INCOME": {
      "agentNotes": [],
      "reportNotes": []
    },
    "SAVINGS": {
      "agentNotes": [],
      "reportNotes": []
    },
    "RENT": {
      "agentNotes": [],
      "reportNotes": []
    },
    "EMPLOYEE_REFERENCE": {
      "agentNotes": [],
      "reportNotes": []
    },
    "LANDLORD_REFERENCE": {
      "agentNotes": [],
      "reportNotes": []
    }
  }
}

Get Rent Passport Notes return all notes which exists on Rent Passport, but only for the sections to which Referencing Request has access

HTTP Request

GET /referencing-requests/client/:clientId/rent-passport/:canopyReferenceId/notes

URL Parameters

Parameter Description
clientId Your client reference
canopyReferenceId The id of registered request on the Canopy side

Revoke Guarantor Request

const axios = require("axios");

const canopyEndpointBaseUri = "canopy_base_uri";
const clientId = "client_id";
const webhookType = "webhook_type";

axios({
  url: `${canopyEndpointBaseUri}/referencing-requests/client/${clientId}/revoke-guarantor-request`,
  method: "DELETE",
  headers: {
    "Authorization": "authorization_token"
    "x-api-key": "api_key",
  },
  data: {
    email: "example@email.com",
});
<?php
$canopyEndpointBaseUri = "canopy_base_uri";
$apiKey = "your_api_key";
$authorizationToken = "authorization_token";
$clientId = "your_client_id";

$client = new \GuzzleHttp\Client();
$response = $client->request(
  "DELETE",
  "$canopyEndpointBaseUri/referencing-requests/client/$clientId/revoke-guarantor-request",
  [
    "headers" => [
      "Authorization" => $authorizationToken,
      "x-api-key" => $apiKey
    ],
    "json" => [
      "email" => "test@email.com"
    ]
  ]
);

echo $response->getBody(), "\n";
?>

The above command returns JSON structured like this:

{
  "success": true
}

This endpoint revokes guarantor request for the specified user.

HTTP Request

DELETE /referencing-requests/client/:clientId/revoke-guarantor-request

URL Parameters

Parameter Description
clientId Your client reference

Body Parameters

Parameter Type Required Description
email string true

Webhooks Endpoints

Register Webhook

const axios = require("axios");

const canopyEndpointBaseUri = "canopy_base_uri";
const clientId = "client_id";

axios({
  url: `${canopyEndpointBaseUri}/referencing-requests/client/${clientId}/webhook/register`,
  method: "POST",
  headers: {
    "Authorization": "authorization_token"
    "x-api-key": "api_key",
  },
  data: {
    type: "PASSPORT_STATUS_UPDATES",
    callbackUrl: "https://callbackurl.com",
    additionalSettings: ["INCOME", "RENT"]
  },
});
<?php
$canopyEndpointBaseUri = "canopy_base_uri";
$apiKey = "your_api_key";
$authorizationToken = "authorization_token";
$clientId = "your_client_id";

$client = new \GuzzleHttp\Client();
$response = $client->request(
  "POST",
  "$canopyEndpointBaseUri/referencing-requests/client/$clientId/webhook/register",
  [
    "headers" => [
      "Authorization" => $authorizationToken,
      "x-api-key" => $apiKey
    ],
    "json" => [
      "type" => "PASSPORT_STATUS_UPDATES",
      "callbackUrl" => "https://callbackurl.com",
      "additionalSettings" => ["INCOME", "RENT"]
    ]
  ]
);

echo $response->getBody(), "\n";
?>

The above command returns JSON structured like this:

{
  "success": true,
  "webhookType": "PASSPORT_STATUS_UPDATES",
  "callbackUrl": "https://callbackurl.com"
}

This endpoint registers the webhook with the appropriate type in Canopy system. After that, Rent Passport updates will be sent to the specified callback url.

HTTP Request

POST /referencing-requests/client/:clientId/webhook/register

URL Parameters

Parameter Description
clientId Your client reference

Body Parameters

Parameter Type Required Description
type string true Specifies which type of updates will be sent from Canopy. Could be one of ["PASSPORT_STATUS_UPDATES", "REQUEST_STATUS_UPDATES", "PASSPORT_ALL_NOTES", "PASSPORT_AGENT_NOTES", "PASSPORT_REPORT_NOTES"].
callbackUrl string true The URL of the webhook endpoint
additionalSettings string[] false List of Rent Passport sections for which updates will be sent. This field should only be added in case "PASSPORT_STATUS_UPDATES" webhook type is specified. The following sections can be specified inside the array: ["INCOME", "RENT", "CREDIT_CHECK", "SAVINGS", "RENTAL_PREFERENCES", "EMPLOYEE_REFERENCE", "LANDLORD_REFERENCE"]. There is no need to explicitly specify the names of all sections if you want to receive all updates, just send an empty array - [].

Event types

If you subscribed to the REQUEST_STATUS_UPDATES type, the updates will be sent to the callbackUrl each time one of the following events trigger:

Event Description
INVITED The user was invited to connect
INVITE_RESENT Resent invitation email for the user
CONNECTED User accepts the connection
CONNECTION_REJECTED User rejects the connection
CONNECTION_STOPPED User stops the connection
SENDING_COMPLETED_PASSPORT_FAILED Sending the completed passport to client failed
PASSPORT_COMPLETED User complete his passport and the document was sent. If a guarantor was requested for this user, the status means that user and guarantor complete their passports and the document with both passports was sent
INVALID_APPLICATION_DETAILS Client's request body with application details was invalid
PASSPORT_COMPLETED_WAITING_FOR_GUARANTOR User complete his passport and the document was sent, but the guarantor requested for this user did not complete his passport
GUARANTOR_REQUEST_CANCELLED The guarantor request for the specified user was cancelled

Once REQUEST_STATUS_UPDATES event trigger, the Canopy should sent the notification to callbackUrl in the following format:

Parameter Type
canopyReferenceId string
clientReferenceId string
notes string

If you are subscribed to the PASSPORT_STATUS_UPDATES type, the updates will be sent to the callbackUrl when one of the following Rent Passport sections is updated (if updates for this section requested):

Section Description
CREDIT CHECK
INCOME
RENT
SAVINGS
LANDLORD REFERENCE optional, only if FULL SCREENING requested
EMPLOYER REFERENCE optional, only if FULL SCREENING requested
IDENTITY VERIFICATION optional, only if identity verification is requested
RTR VERIFICATION optional, only if right to rent is requested

Notification format

Once PASSPORT_STATUS_UPDATES event trigger, the Canopy should sent the notification to callbackUrl in the following format:

Parameter Type Description
canopyReferenceId string
clientReferenceId string
updatedSection object Object with updated section data. See updatedSection data structure below
instant object Object with instant data. See instant data structure below
full object Optional field, sent only if FULL SCREENING requested. See full data structure below
identityVerification string Optional field, sent only if identity verification is requested. One of ["NOT_STARTED", "IN_PROGRESS", "DONE"]
rightToRent string Optional field, sent only if right to rent is requested. One of ["NOT_STARTED", "IN_PROGRESS", "DONE"]
globalStatus string One of ["NOT_STARTED", "IN_PROGRESS", "ACCEPT", "CONSIDER", "HIGH_RISK"]

updatedSection data structure:

Parameter Type Description
type string One of ["INCOME", "RENT", "CREDIT_CHECK", "SAVINGS", "EMPLOYEE_REFERENCE", "LANDLORD_REFERENCE", "IDENTITY_VERIFICATION", "RTR_VERIFICATION"]
newStatus string One of ["NOT_STARTED", "IN_PROGRESS", "DONE"]
updatedAt ISODateTime

instant data structure:

Parameter Type Description
status string One of ["NOT_STARTED", "IN_PROGRESS", "ACCEPT", "CONSIDER", "HIGH_RISK"]
sections object Object with sections data. See instant sections data structure below

Instant sections data structure:

Parameter Type Description
income string One of ["NOT_STARTED", "IN_PROGRESS", "DONE"]
rent string One of ["NOT_STARTED", "IN_PROGRESS", "DONE"]
creditCheck string One of ["NOT_STARTED", "IN_PROGRESS", "DONE"]
savings string One of ["NOT_STARTED", "IN_PROGRESS", "DONE"]

full data structure:

Parameter Type Description
status string One of ["NOT_STARTED", "IN_PROGRESS", "ACCEPT", "CONSIDER", "HIGH_RISK"]
sections object Object with sections data. See full sections data structure below

Full sections data structure:

Parameter Type Description
employerReference string One of ["NOT_STARTED", "IN_PROGRESS", "DONE"]
landlordReference string One of ["NOT_STARTED", "IN_PROGRESS", "DONE"]

If you subscribed to the PASSPORT_ALL_NOTES | PASSPORT_AGENT_NOTES | PASSPORT_REPORT_NOTES type, the updates will be sent to the callbackUrl when one of the folowing notes updated(if webhook type require this note type and client has acces to noted section):

Section Description
AGENT_NOTE
REPORT_NOTE

Once ADMIN_NOTE_CHANGED event trigger, the Canopy should sent the notification to callbackUrl in the following format:

Parameter Type Description
canopyReferenceId string
clientReferenceId string
noteType string One of [AGENT, REPORT]
section string One of [SUMMARY, INCOME, RENT, CREDIT_CHECK, RENTAL_PREFERENCES, EMPLOYEE_REFERENCE,
LANDLORD_REFERENCE, SAVINGS, RIGHT_TO_RENT]
action string One of ["CREATED", "EDITED", "DELETED"]
note string

Unregister Webhook

const axios = require("axios");

const canopyEndpointBaseUri = "canopy_base_uri";
const clientId = "client_id";
const webhookType = "PASSPORT_STATUS_UPDATES";

axios({
  url: `${canopyEndpointBaseUri}/referencing-requests/client/${clientId}/webhook/${webhookType}`,
  method: "DELETE",
  headers: {
    "Authorization": "authorization_token"
    "x-api-key": "api_key",
  }
});
<?php
$canopyEndpointBaseUri = "canopy_base_uri";
$apiKey = "your_api_key";
$authorizationToken = "authorization_token";
$clientId = "your_client_id";

$client = new \GuzzleHttp\Client();
$response = $client->request(
  "DELETE",
  "$canopyEndpointBaseUri/referencing-requests/client/$clientId/webhook/$webhookType",
  [
    "headers" => [
      "Authorization" => $authorizationToken,
      "x-api-key" => $apiKey
    ]
  ]
);

echo $response->getBody(), "\n";
?>

The above command returns JSON structured like this:

{
  "success": true,
  "webhookType": "PASSPORT_STATUS_UPDATES"
}

This endpoint unregisters webhook and stops sending Rent Passport updates to the specified callback URL.

HTTP Request

DELETE /referencing-requests/client/:clientId/webhook/:webhookType

URL Parameters

Parameter Description
clientId Your client reference
webhookType The type of the webhook you want to unregister

Errors

We use conventional HTTP response codes to indicate the success or failure of an API request, typically one of:

Error Code Meaning
2xx Indicates success
4xx Indicates a client error with information provided (missign required parameters etc..)
5xx Indicates an error with the Canopy platform

We suggest you wrap 4xx errors in your client and provide a user friendly message to your end users as part of the integration.