Enneo

Customer Recognition

Searching for Features

Searching for Features

Data handed over to your code by enneo (Input)

{
  "customerId": "C-abcd-123",
  "contractId": "123",
  "my-custom-parameter": "abcd", // to be configured in enneo settings
  "any-other-parameter-the-ai-found": "abcd",
  "ticketId": 123, // always provided natively by enneo, regardless of AI search results
}

Variables names such as customerId or my-custom-parameter can be defined by yourself under Settings -> Customer and Contract Search -> Customer Identification via AI -> Identification Feature Search Parameter, including the prompt used for detection. The above values are examples.

Data structure expected by enneo as return value (Output)

{
  "contractId": "100001", // contract id of the found contract. cannot be null
  "customerId": "200001", // customer id associated to the found contract. cannot be null
}

Notes:

  • enneo always expects exactly one or no contract. If the search parameters deliver more than one result, it is necessary to narrow down the search to a single result before calling enneo.
  • If a contract is found, both IDs must be returned - contractId and customerId. If one of them is missing, enneo rejects the result with an error.
  • If the search doesn't provide any results, enneo expects null (recommended), an empty object ({}) or an empty array ([]). All options imply "no contract found" and are not considered an error.
  • If you want to override the enneo legitimization, you can add the properties customerLegitimation and customerLegitimationMessage. Further information can be found on the corresponding documentation page ([/de/system-integration/customer-recognition/contract-legitimation]).

Multi-level contract structures

Enneo knows two levels: customer and contract. The contractId does not have to be the contract number from your ERP. It is the key under which enneo stores and caches the contract, and which it later passes unchanged to searchContractByIdExecutor. What it identifies is up to you.

This helps when a request concerns an object below the contract, such as a single delivery point in a bundle contract with thousands of delivery points, or when it matters which contact person is writing. Return a contractId that identifies that object as well:

  • Composite ID: join the parts with a fixed separator, e.g. BV-1001_48213 for bundle contract BV-1001 and delivery point 48213. Your searchContractByIdExecutor splits the ID again and loads the data of exactly that delivery point.
  • Search ID: an ID of your own that your system maps to the parts in a mapping table.

If the request concerns the bundle contract as a whole, return just BV-1001. The level of detail then follows from each ticket.

Keep in mind:

  • Everything that affects the loaded data belongs in the contractId. Enneo caches contracts per contractId, so tickets about different delivery points get separate data.
  • The customerId stays the same for all variants, e.g. the bundle customer number. Enneo checks that the customerId of the loaded contract matches the one from the search.
  • The same object always yields the same ID: a fixed order of parts and a separator that does not occur within them.
  • At most 50 characters for contractId and customerId.
  • Each variant appears in enneo as a contract of its own. AI agents receive the contractId and can pass it to your ERP unchanged or split it themselves.

Example Implementation

The example calls a demo ERP. Replace <DEMO_TOKEN> with the demo token from the default code under Settings → Integration into Systems → Customer and Contract Search in your Enneo instance. The token only unlocks the demo data; your own system uses its own authentication instead.

<?php

// Load enneo SDK. Input is made available through $in
use EnneoSDK\ApiEnneo;
use EnneoSDK\Setting;
use EnneoSDK\Api;
require(getenv()['SDK']);

// These variables can be detected by the AI, if provided by the customer in his request
// If you leave the default setting unchanged, $in contains:
// $in->contractId, $in->customerId, $in->firstname, $in->lastname, $in->phone, $in->company, $in->email, $in->meterNumber, $in->address, $in->postalCode, $in->city
// But feel free to change them, and the detection prompt, to your own needs. Head over to the setting at "Customer and contract search" -> "Search Parameters Customer Identification"


// Get additional metadata from the ticket that you might need, e.g. channel-dependent authorization
// Note: Customer identification and thus this code is one of the first steps in ticket processing, so tags, ai agents, sentiment etc. are not (yet) available and will therefore not be included by the ticket API call
try {
//$ticket = ApiEnneo::get('/api/mind/ticket/'.$in->ticketId);
} catch (Exception $e) {}

// You can also do preprocessing, e.g. input manipulation operations, as in this example
if (isset($in->contractId) && ($in->contractId == 7155559 || $in->contractId == 71559)) {
$in->contractId = 715559;
}

// Do we have a contract id? Then let's do a direct contract search
if ($in->contractId ?? false) {
// Search for the contract id
try {
    $contract = ApiEnneo::get('/api/mind/contract/'.$in->contractId);
} catch (Exception $e) {$contract = null;}
if ($contract?->id) {
    echo json_encode(['contractId' => $contract->id, 'customerId' => $contract->customerId]);
    exit;
}
}

// Do we have a customer id? Then let's do a direct customer search and return the first contract
if ($in->customerId ?? false) {
try {
    $customer = ApiEnneo::get('/api/mind/customer/byCustomerId/'.$in->customerId);
} catch (Exception $e) {$customer = null;}
if ($customer?->id) {
    foreach ($customer->contractIds as $contractId) {
        echo json_encode(['contractId' => $contractId, 'customerId' => $customer->id]);
        exit;
    }
}
}

// Maybe the LLM just detected a customer ID as contract ID and vice versa. Let's try
if ($in->contractId ?? false) {  // Case the LLM found a contract ID that actually was a customer ID
try {
    $customer = ApiEnneo::get('/api/mind/customer/byCustomerId/'.$in->contractId);
} catch (Exception $e) {$customer = null;}
if ($customer?->id) {
    foreach ($customer->contractIds as $contractId) {
        echo json_encode(['contractId' => $contractId, 'customerId' => $customer->id]);
        exit;
    }
}
}
if ($in->customerId ?? false) { // Case the LLM found a customer ID that actually was a contract ID
try {
    $contract = ApiEnneo::get('/api/mind/contract/'.$in->customerId);
} catch (Exception $e) {$contract = null;}
if ($contract?->id) {
    echo json_encode(['contractId' => $contract->id, 'customerId' => $contract->customerId]);
    exit;
}
}

// Otherwise, let's pass on the remaining search terms to a user-defined API
// Add your handling code here that finds the customers based on the input data
$contractRawData = Api::call(method: 'GET', url: 'https://admin.enneo.ai/api/seed-contracts/search?token=<DEMO_TOKEN>&' . http_build_query($in));
if ($contractRawData) {
$results = [
    'contractId' => $contractRawData[0]->id, // e.g. "123". Mandatory to be provided
    'customerId' => $contractRawData[0]->customerId, // e.g. "C-456789". Mandatory to be provided
];
} else {
$results = [];
}

// Note: You may choose to override the default enneo legitimation. In this case, you can set these variables. Further details are available at https://docs.enneo.ai/de/system-integration/customer-recognition/contract-legitimation
//$searchResults[0]['customerLegitimation'] = 10; // Either 0, 10, 20 or 30, see the docs for further information
//$searchResults[0]['customerLegitimationMessage'] = "Customer did not provide his special code";

// Finally, return the matching contracts in this data structure
echo json_encode($results);