Skip to content

The customer lookup contract

Before oHallo can verify a caller, link a conversation to a customer, or send a record-bound verification link, it has to find the customer in your own system. That lookup runs first, on every inbound message and every call. This page is the contract for the tool that answers it.

Your customers live in your systems, not in oHallo. oHallo reads a customer from those systems when it needs one and stores only your customer reference. The operator sets up, on the connection’s page under Settings, Customer records, in the rows By email address and By phone number under Finding customers, which of your tools finds a customer, which tool input receives the email address or the phone number, which field in the answer holds your customer reference, and which field in the same tool’s answer holds the email address or the phone number itself: National eID checks the answer by it, the email link and the email sender address tie a customer to their own record through the field with the email address, and Caller ID through the field with the phone number. The platform then calls exactly what was designated, with exactly the argument name you declared, and nothing else.

A tool that returns one of your records, such as an order, can carry an email link too: the field in its answer that holds the email address, and optionally your customer reference, set up under Customer data with Add an email link. In chat, a request to see that record on a tool declared A customer and set to identified then sends the email link to the address the record holds, and pressing it ties the customer to that record.

A lookup tool needs four things:

  1. An input for an email address, an input for a phone number, or both. The names are yours; the operator designates them. The designation is validated against your declared inputSchema, so declare one.
  2. A stable customer reference in the response. The identifier your own systems use for this customer, at a field path the operator can designate. It must not change when the customer renews, moves, or changes contact details. Return it as a string, or as a JSON number that is a whole number from 0 to 9007199254740991. It is your own customer number, never a personal identity number: oHallo does not keep a reference that has the form of a Danish CPR number, a Swedish personnummer or coordination number, or a Norwegian fødselsnummer or D-number, with or without separators, or that equals the person identifier or the national identity number in the same answer, where the operator designated those fields on the same tool; the comparison ignores separators, letter case and the leading zeros of a value made of digits only. A JSON number outside that range is not kept either. In each of these cases the customer is still recognised, no reference is stored, and the lookup records customer_ref_refused. Its field is never the field that holds the email address, the phone number or the national identity number on the same tool: oHallo refuses that designation.
  3. A declared outputSchema. The designation surface derives the pickable fields from it. A tool without one can still be designated, but the operator has to type field paths by hand and a typo only surfaces at runtime.
  4. A clear answer when the person is unknown. Answer with a result marked isError whose first content item is a text item holding JSON with "error_type": "not_found", or return an empty object. The platform records either as “your system does not know this person”, which is a normal outcome, not a fault. Any other answer, one that is not empty and carries neither your customer reference nor not_found, cannot be read: on email, where the lookup by the sender’s address finds the record a National eID verification link is compared with, and the record the email link and the email sender address tie the sender to, no link is issued and no one is tied; on a call, Caller ID ties no one. When more than one of your customers holds the email address or phone number, return every match as a list in place of the single record: the designated path then holds no single customer reference, so oHallo keeps none for that address or number, starts no National eID verification through it and ties no one to it.

The tool declaration:

{
"name": "find_contact",
"description": "Find a contact by email address or phone number.",
"inputSchema": {
"type": "object",
"properties": {
"email": { "type": "string" },
"phone": { "type": "string" }
}
},
"outputSchema": {
"type": "object",
"properties": {
"contact_id": { "type": "string" },
"account_id": { "type": "string" },
"name": { "type": "string" },
"email": { "type": "string" },
"phone": { "type": "string" }
},
"required": ["contact_id", "name", "email"]
}
}

What the platform sends when a new caller phones in, assuming the operator designated phone as the phone input, and email and phone as the fields that hold the email address and the phone number:

{ "phone": "+447700900123", "tenantId": "...", "workspaceId": "..." }

What your tool answers:

{
"contact_id": "CON-001",
"account_id": "ACC-001",
"name": "Alex Doe",
"email": "alex.doe@example.com",
"phone": "+44 7700 900123"
}

The platform reads the designated reference field (contact_id here), stores it as the contact’s external reference, and discards the rest of the payload apart from name and company fields used for the operator’s screen. The answer never reaches a language model. For National eID it also reads the designated phone field, which holds the number it sent: +44 7700 900123 and +447700900123 are the same number.

For a business customer the operator can also designate, on the lookup the National eID rows use, the field that holds the company account number: your own number for the company the customer belongs to. When the customer verifies, oHallo reads it from the same answer the customer was proven against and ties the customer to that one company account, beside their customer reference. Return one value per record, as a string or a JSON number with the same rules as the customer reference. A list, an object, an empty value or a value in the form of a personal identity number ties no company, and the lookup records account_ambiguous. A record that names no company ties none. When your lookup returns the company’s own record, the company account number may be the same field as the customer reference.

Beside it the operator can designate the field that holds that company’s registration number, in Denmark the eight-digit CVR number. oHallo compares the company number of a MitID Erhverv login with it and, on a match, ties the employee to the company account number read from the same answer, never to a customer reference. Return one value per record, as a string or a JSON number; spaces, dots, hyphens and a leading DK are ignored. oHallo keeps neither registration number, the record’s or the login’s: it compares keyed hashes and discards the values. The company account number is kept as the proven tie. A record that names no company account number ties no company, whatever registration number it holds.

The tools that return your records name their owner the same way. Each record of a tool declared at the customer level carries, at the owner field the operator declares, the customer reference this lookup returns (contact_id in the example above), and the company account number when it names one (account_id). Each record of a tool declared at the company level carries the company account number at its owner field, and a field that names the customer who placed it, declared as another party. A tool that changes something, or that returns a list, takes an input that receives the number: oHallo fills it with the verified customer’s own customer reference, or their company’s account number, on every call, and your tool answers about that customer only. Answer a record of another customer exactly as one that does not exist (not_found). An error about the caller’s own record, such as an order that has already shipped, names the record’s owner like any other answer, or it is withheld:

{
"error_type": "order_not_eligible",
"reason": "Order ORD-001 cannot be cancelled: it is already delivered",
"order_id": "ORD-001",
"contact_id": "CON-001",
"account_id": "ACC-001"
}

oHallo’s own identifiers never reach your tools: a tool receives tenantId and workspaceId and the arguments the request made. The lookup is read to find and verify customers whether or not the operator declares its scope; an agent calls it, like any tool, only once it is declared.

On chat, a request to a tool declared at the customer or company level that names a record, such as cancel_order with an order_id, is verified against the record a lookup on the same connection finds from the request’s own arguments. The lookup is the requested tool itself when the operator set the National eID rows up on it, or else the one tool on that connection with those rows that takes one of the request’s arguments. oHallo calls it with only the arguments it declares in its inputSchema, and matches an argument name to an input whatever its letter case and underscores, so orderId reaches order_id. When two such tools take the request’s arguments, or none does, no lookup is called and a colleague takes over the request.

Declare each lookup’s inputs. A tool that declares no input properties takes any argument, so beside another lookup that takes the request’s argument neither is chosen. With find_customer taking email and phone, and get_order taking order_id, a request to cancel_order with an order_id is verified through get_order.

  • Email: the sender address as it arrived on the channel.
  • Phone: the caller’s number as the carrier delivered it, in E.164 form, for example +447700900123.

Match an email address that is identical apart from the case of the letters A to Z: every other character as sent, in the local part and the domain. Match a phone number in any written form: any spacing, punctuation, international or national form. Answer only the records that hold that exact address, or that number with all of its digits.

For National eID, and for the email link, the email sender address and Caller ID where they tie a customer to their own record, the operator designates, on the lookup’s own row (By email address, By phone number), the field in the answer that holds the email address or the phone number, and oHallo uses the record only when that field holds the value it looked up: the same address, identical apart from the case of the letters A to Z, with surrounding spaces or a display name; or the same number in any written form. A number that begins with + or 00 names its own country. Any other number is read in the country of the workspace’s eID connection, and only where it is no valid number there do its digits alone decide, so 4612345678 is +46 12 34 56 78 and 49 33 62 52 is the Danish +45 49 33 62 52. For Caller ID on a workspace whose eID connections name no single country, only a number that begins with + or 00 ties a caller. A field may hold a list of addresses or numbers, of which one must be the value looked up. When a record holds several addresses or numbers in separate fields, designate the field your tool matches on, so the record your tool returns holds the value there.

The operator classifies every tool’s risk in the MCP hub. A lookup tool is typically read_pii, because it returns personal data. The resolution read runs on a server-side reader lane that satisfies the assurance gate for designated tools, so a read_pii classification does not block the lookup.

Two rules follow from the lane being read-only:

  • A tool classified write or destructive is refused as a lookup. Keep the lookup a pure read.
  • A tool with no risk classification at all is treated as a write and refused too. Classify it read or read_pii.

The lookup carries no idempotency key and takes no dispatch claim, because it must be free of side effects. The platform may call it repeatedly for the same person. Do not create records, send notifications, or mutate state in a lookup tool.

The connection’s section under Settings, Customer records shows the state of the lookup in plain language, including the latest outcome recorded against the designated tool:

OutcomeMeaning
resolvedYour tool answered with a customer reference.
not_foundYour tool answered, and does not know this person. Normal.
no_designationNo lookup is designated for this channel yet.
tool_not_foundThe designated tool is no longer offered by the connection.
field_missingThe answer carried no value at a designated field: the reference, or for National eID the field that holds the email address or phone number.
customer_ref_refusedThe value at the designated reference path is not kept as a customer reference (see item 2 above). The customer is recognised without it. Designate the field that holds your own customer number.
account_ambiguousA National eID verification read the record, and the field designated for the customer’s company account number did not hold one account: a list or an object, an empty value, or a value with the form of a personal identity number. No company is tied to the customer. Designate the field that holds the one account the customer belongs to.
record_mismatchA National eID verification looked a customer up by email address or phone number, and the field designated for that value held another address or number. The verification leaves that record aside. Designate the field that holds the value your tool matches on, and answer only records that hold it.
lookup_gatedPlatform policy refused the read. Usually the tool’s risk classification: classify it read or read_pii.
upstream_errorYour system returned an error or timed out.

Without a working lookup, the assistant cannot recognise a returning customer and cannot send a verification link to the address on your record. The lookup is the foundation the rest of identity builds on.