How to onboard Supply Partner with Karhoo API

🚧

Credentials Required

Working directly with the Karhoo APIs requires valid credentials (an API key).
Contact Karhoo to gain full access to the platform.

This tutorial guides you through importing/onboarding supply partners through the Karhoo API.

This guide focuses on importing and deleting supply partners that use an existing dispatch. There is no possibility to edit imported supply partners through public API.

Requirements

  • Valid API credentials that allow you to play with the Karhoo API. Please contact Karhoo to achieve them. The account should have access groups that allow importing of supply partners.
  • A configured dispatch in the Karhoo platform and access with the credentials mentioned above.
  • A configured Shared Secret in dispatch configuration used to encrypt (with JWE ( JSON Web Encryption ) Standard) all data passed between the importer and the Karhoo platform. Please Contact Karhoo to set it.

Importing new supply partners into the platform

Prepare data

To integrate with the system, developers are required to prepare data in compliance with the provided schema. This involves structuring the data according to the outlined schema, ensuring all required fields are included and appropriately formatted.

  • Ensure all mandatory fields are populated.
  • Validate data against the provided schema to guarantee compatibility.
  • Encode any optional fields as null if they are not applicable.

📘

Values formatting

  • All monetary amounts are represented in the format multiplied by 100. (e.g., 1030 represents 10,30 EUR).
  • All time-related values, such as lead times, are expressed in ISO 8601 duration format (e.g., P1Y1M1DT1H1M1 represents 1 year, 1 month, 1 day, 1 hour, 1 minute. )

Data sections

Details: Contains information related to the supply partner's tax registration and identification number. The "is_tax_registered" field indicates whether the partner is registered for tax purposes, and the "tax_number" field holds the tax identification number.

Products (Fleets): Provides details about the products offered by the supply partner, including their names, descriptions, booking settings, and associated vehicles.

📘

Vehicles

The schema does not contain specific enum values that are allowed in the platform. You can find more information here

Profile: General information about the supply partner, such as the number of vehicles and logo URL.

Settlements: Includes details about the settlement process, including the regulation type and VAT exclusion status. The is_vat_excluded field indicates whether VAT is excluded from settlements.

Contract: Contains information about the contractual agreement between the supply partner and the platform, including company details, tax information, and bank details.

Users: (Optional) Lists the users associated with the supply partner, including their email addresses and roles. If additional user accounts are not required, this section may be omitted. All users provided here will be invited by the email notification.

🚧

Adding existing users

Users who are already registered in the Karhoo platform will be attached into the newly formed partner organization, instead of creating new user accounts

Schema

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "id": { "type": "string" },
    "name": { "type": "string" },
    "profile": {
      "type": "object",
      "properties": {
        "number_of_vehicles": { "type": "integer" },
        "logo_url": { "type": ["string", "null"] }
      },
      "required": ["number_of_vehicles"]
    },
    "settlements": {
      "type": "object",
      "properties": {
        "regulation_type": {
          "enum": ["REGULATED_TAXI", "PRIVATE_HIRE_VEHICLE"]
        },
        "custom_regulation_type": {
          "enum": [
            "REGULATED_TAXI",
            "PRIVATE_HIRE_VEHICLE",
          ]
        },
        "is_vat_excluded": { "type": "boolean" }
      },
      "required": ["regulation_type", "is_vat_excluded"]
    },
    "products": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "profile": {
            "type": "object",
            "properties": {
              "name": { "type": "string" },
              "description": { "type": "string" },
              "support_email": { "type": "string" },
              "support_phone_number": { "type": "string" }
            },
            "required": ["name", "description", "support_phone_number"]
          },
          "booking": {
            "type": "object",
            "properties": {
              "currency": { "type": "string" },
              "country": { "type": "string" },
              "locale": { "type": "string" },
              "booking_types": {
                "type": "array",
                "items": { "type": "string" }
              },
              "fare_type": { "type": "string" },
              "prebook_min_lead_time": { "type": "string" },
              "prebook_max_lead_time": { "type": "string" },
              "base_address": {
                "type": "object",
                "properties": {
                  "position": {
                    "type": "object",
                    "properties": {
                      "latitude": {
                        "type": "number",
                        "minimum": -90,
                        "maximum": 90
                      },
                      "longitude": {
                        "type": "number",
                        "minimum": -180,
                        "maximum": 180
                      }
                    },
                    "required": ["latitude", "longitude"]
                  },
                  "address": {
                    "type": "object",
                    "properties": {
                      "country": { "type": "string" },
                      "state_or_province": { "type": "string" },
                      "city": { "type": "string" },
                      "postal_code": { "type": "string" },
                      "street": { "type": "string" },
                      "house_number_or_name": { "type": "string" }
                    }
                  }
                }
              }
            },
            "required": [
              "currency",
              "country",
              "locale",
              "booking_types",
              "fare_type",
              "base_address"
            ]
          },
          "integration": { "type": ["object", "null"] },
          "labels": { "type": "array", "items": { "type": "string" } },
          "vehicles": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": { "type": "string" },
                "dms_vehicle_type": { "type": "string" },
                "type": { "type": "string" },
                "service_level": { "enum": ["standard", "premium"] },
                "engine_type": { "type": "string" },
                "passenger_capacity": { "type": "integer" },
                "luggage_capacity": { "type": "integer" },
                "taxi": { "type": "boolean" },
                "wheelchair_accessible": { "type": "boolean" },
                "disabled": { "type": "boolean" }
              },
              "required": [
                "dms_vehicle_type",
                "type",
                "service_level",
                "engine_type",
                "passenger_capacity",
                "luggage_capacity",
                "taxi",
                "wheelchair_accessible",
                "disabled"
              ]
            }
          }
        }
      }
    },
    "contract": {
      "type": "object",
      "properties": {
        "company_details": {
          "type": "object",
          "properties": {
            "country": { "type": "string" },
            "legal_business_name": { "type": "string" },
            "doing_business_as": { "type": "string" },
            "registration_number": { "type": "string" },
            "phone_numbers": {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "type": {
                    "enum": [
                      "GENERAL",
                      "BILLING",
                      "OPERATIONAL",
                      "SUPPORT",
                      "REMITTANCE"
                    ]
                  },
                  "value": { "type": "string" }
                },
                "required": ["type", "value"]
              }
            },
            "email_addresses": {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "type": {
                    "enum": [
                      "GENERAL",
                      "BILLING",
                      "OPERATIONAL",
                      "SUPPORT",
                      "REMITTANCE"
                    ]
                  },
                  "value": { "type": "string" }
                },
                "required": ["type", "value"]
              }
            },
            "web_address": { "type": "string" },
            "address": {
              "type": "object",
              "properties": {
                "registration": {
                  "type": "object",
                  "properties": {
                    "country": { "type": "string" },
                    "state_or_province": { "type": "string" },
                    "city": { "type": "string" },
                    "postal_code": { "type": "string" },
                    "street": { "type": "string" },
                    "house_number_or_name": { "type": "string" }
                  }
                },
                "billing": {
                  "type": "object",
                  "properties": {
                    "country": { "type": "string" },
                    "state_or_province": { "type": "string" },
                    "city": { "type": "string" },
                    "postal_code": { "type": "string" },
                    "street": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "tax_details": {
          "type": "object",
          "properties": {
            "is_tax_registered": { "type": "boolean" },
            "tax_number": { "type": "string" }
          },
          "required": ["is_tax_registered"]
        }
      }
    },
    "bank_details": {
      "type": "object",
      "properties": {
        "bank_accounts": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "owner_name": { "type": "string" },
              "country": { "type": "string" },
              "iban": { "type": "string" },
              "currency": { "type": "string" },
              "account_type": { "enum": ["Checking", "Savings"] },
              "account_number": { "type": "string" },
              "branch_code": { "type": "string" },
              "bank_name": { "type": "string" },
              "bic_swift_code": { "type": "string" },
              "description": { "type": "string" }
            },
            "required": [
              "owner_name",
              "country",
              "iban",
              "currency",
              "account_type",
              "account_number",
              "branch_code",
              "bank_name",
              "bic_swift_code",
              "description"
            ]
          }
        }
      }
    }
  },
  "required": ["id"]
}

Example data

{
  "id": "SP123",
  "name": "ABC Transportation",
  "profile": {
    "number_of_vehicles": 20,
    "logo_url": "https://example.com/logo.png"
  },
  "settlements": {
    "regulation_type": "REGULATED_TAXI",
    "is_vat_excluded": true
  },
  "products": [
    {
      "id": "P123",
      "profile": {
        "name": "Standard Service",
        "description": "Basic transportation service",
        "support_email": "[email protected]",
        "support_phone_number": "+1234567890"
      },
      "booking": {
        "currency": "PLN",
        "country": "PL",
        "locale": "pl-PL",
        "booking_types": ["on_demand", "pre_book"],
        "fare_type": "fixed",
        "prebook_min_lead_time": "900",  // 15 minutes lead time
        "prebook_max_lead_time": "86400", // 1 day lead time
        "base_address": {
          "position": {
            "latitude": 52.2297,
            "longitude": 21.0122
          },
          "address": {
            "country": "PL",
            "city": "Warsaw",
            "postal_code": "00-001",
            "street": "Main Street",
            "house_number_or_name": "1"
          }
        }
      },
      "labels": ["open-marketplace"],
      "vehicles": [
        {
          "name": "Standard Sedan",
          "dms_vehicle_type": "sedan",
          "type": "standard",
          "service_level": "standard",
          "engine_type": "gasoline",
          "passenger_capacity": 4,
          "luggage_capacity": 2,
          "taxi": false,
          "wheelchair_accessible": false,
          "disabled": false
        }
      ]
    }
  ],
  "contract": {
    "company_details": {
      "country": "PL",
      "legal_business_name": "ABC Transportation Sp. z o.o.",
      "registration_number": "123456789",
      "address": {
        "registration": {
          "country": "PL",
          "city": "Warsaw",
          "postal_code": "00-001",
          "street": "Main Street",
          "house_number_or_name": "1"
        }
      }
    },
    "tax_details": {
      "is_tax_registered": true,
      "tax_number": "123-456-789"
    },
    "bank_details": {
      "bank_accounts": [
        {
          "owner_name": "ABC Transportation Sp. z o.o.",
          "country": "PL",
          "iban": "PL61109010140000071219812874",
          "currency": "PLN",
          "account_type": "Checking",
          "account_number": "71219812874",
          "branch_code": "10901014",
          "bank_name": "Bank Polski",
          "bic_swift_code": "WBKPPLPP",
          "description": "Main Operating Account"
        }
      ]
    }
  }
}

Encrypt Data with JWE

Once the data is prepared, it must be encrypted using the assigned shared secret key to ensure secure transmission. JWE should be utilized for encryption, providing confidentiality and integrity protection during data transfer.

Encryption Procedure:

  • Generate a JWE token using the prepared data.
  • Encrypt the data payload with the shared secret key.
  • Verify encryption parameters comply with security requirements and standards.

The data will be securely encrypted and ready for transmission within the system by following these steps.

Examples of encryption

These snippets provide practical implementations for encrypting data to ensure confidentiality and integrity during communication between systems.

const { createHash } = require('crypto');
const { compactDecrypt } = require('jose');

async function encryptData(data, sharedSecret) {
    // Hash the shared secret using SHA-256
    const secretKeyHash = createHash('sha256').update(sharedSecret).digest();

    // Convert data to JSON string
    const payloadString = JSON.stringify(data);

    // Encrypt the payload using JWE
    const jwe = await compactEncrypt(payloadString, secretKeyHash);

    return jwe;
}

// Example usage
const dataToEncrypt = {
    id: "12345",
    name: "Sample Partner",
    profile: {
        number_of_vehicles: 10,
        logo_url: "https://example.com/logo.png"
    },
    // Other data fields...
};

const sharedSecret = 'your_shared_secret_key_here';

encryptData(dataToEncrypt, sharedSecret)
    .then(jwe => {
        console.log('Encrypted Data:', jwe);
    })
    .catch(error => {
        console.error('Encryption Error:', error);
    });
package main

import (
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"github.com/square/go-jose/v3"
)

func encryptData(data interface{}, sharedSecret string) (string, error) {
	// Convert data to JSON string
	payloadBytes, err := json.Marshal(data)
	if err != nil {
		return "", err
	}
	payloadString := string(payloadBytes)

	// Hash the shared secret using SHA-256
	secretKeyHash := sha256.Sum256([]byte(sharedSecret))

	// Encrypt the payload using JWE
	encrypter, err := jose.NewEncrypter(jose.A256GCM, jose.Recipient{
		Algorithm: jose.DIRECT,
		Key:       []byte(secretKeyHash[:]),
	}, nil)
	if err != nil {
		return "", err
	}

	encryptedData, err := encrypter.Encrypt([]byte(payloadString))
	if err != nil {
		return "", err
	}

	return encryptedData.CompactSerialize()
}

func main() {
	// Example data to be encrypted
	dataToEncrypt := map[string]interface{}{
		"id":     "12345",
		"name":   "Sample Partner",
		"profile": map[string]interface{}{
			"number_of_vehicles": 10,
			"logo_url":           "https://example.com/logo.png",
		},
		// Other data fields...
	}

	// Shared secret key
	sharedSecret := "your_shared_secret_key_here"

	// Encrypt data
	jwe, err := encryptData(dataToEncrypt, sharedSecret)
	if err != nil {
		fmt.Println("Encryption Error:", err)
		return
	}

	fmt.Println("Encrypted Data:", jwe)
}

This JavaScript/Go examples demonstrate how to encrypt data using the provided encryption function compactEncrypt. Ensure to replace 'your_shared_secret_key_here' with your actual shared secret key.

Send request with encrypted data

To send the encoded data to the Karhoo API, you should submit it via an HTTP request.

Required data

  • Dispatch partner ID - identifier in Karhoo platform
  • External id of the supply partner in your platform
  • Encrypted JWE data generated previously

Sample request to the API

POST https://rest.karhoo.com/v1/partners/:dispatchPartnerId/dispatch/discovery/supply-partners/:externalId
Authorization: ApiKey <dispatch api key>
{
    "jwe": "eyJlb...fKayjGfw"
}

The definition of the endpoint used above is Partners API

📘

Asynchronous import

Keep in mind that part of the data used to import suppy partner can be propagated asynchronously and won't be available instantly after import request.

Handling results

Upon successful completion of the HTTP request to the Karhoo API endpoint, if no critical issues occur, the system will assign a unique identifier to the supply partner and all of its defined supply products (fleets). Any errors that do not directly result in the termination of the import will be provided under the warnings key in the response.

The imported user can then log in to one of the created administrative accounts (if any have been set up) in the Partner Portal and fill in any missing data imported.

If you have any questions or encounter any issues during the import process, contact our support team for assistance.

The definition of the endpoint response is available in Partners API

Disabling imported supply partner

If a supply partner is no longer in use on the external platform, it can be deactivated using an API request. This process involves sending a request to the Karhoo API to disable the respective supply partner. Below is the format of the request:

Required data

  • Dispatch partner ID - identifier of the dispatch in Karhoo platform
  • External identifier of the supply partner in your platform
DELETE https://rest.karhoo.com/v1/partners/:dispatchPartnerId/dispatch/discovery/supply-partners/:externalId
Authorization: ApiKey <dispatch api key>

Editing Imported Supply Partners:

At present, the only available action for imported supply partners is inviting new users to join the existing supply partner. The schema for the endpoint mirrors that of creating a new partner and requires encryption of input data in the same manner.

Here's an example of how to invite new users to the existing supply partner:

PUT https://rest.karhoo.com/v1/partners/:dispatchPartnerId/dispatch/discovery/supply-partners/:externalId
Authorization: ApiKey <dispatch api key>
{
    "jwe": "eyJlb...fKayjGfw"
}

The definition of the endpoint response is available in Partners API

Ensure that the input data is encrypted following the specified encryption method. All keys except for the users will be disregarded. Attempting to modify any other aspect of the existing user will result in an error.

Contact us

If you have any questions or encounter any issues during the import process, feel free reach out to our team for assistance