Connect your back office
A data connector is a read-only API of your system that serves the card of an account and a few lists (views). Lelemon calls it signed, shows what comes back and gives the same data to the support copilot as tools. Nothing you return is stored: only who looked is recorded.
Every integrator has a different back office, so Lelemon knows nothing about yours. The format is the contract.
- The copilot uses the tool that finds an account by email.
- Lelemon calls your connector, signed in Lelemon-Signature with its own domain, valid for 60 seconds.
- Your system answers with the matches. The stable id is what you keep as the externalRef of the contact.
- Lelemon asks for the card of that account.
- The card is a list of sections. You mark personal data with sensitive: true and special category data with specialCategory: true.
- Lelemon masks sensitive fields and anything shaped like a RUT, email, phone, card or secret. Nothing is stored.
- The copilot reads the card. The tools of a connector are noPersist: traces keep only status and duration.
- To see one value, a key with connectors:reveal calls reveal for that field. Fields marked specialCategory cannot be revealed.
- The access is recorded with the key or member, the connector, the view and the field. The account is a keyed hash and no value is stored. List the rows with GET /v2/data-connectors/{id}/accesses.
What you serve
Four documents, all JSON, all under your base URL. Download the JSON Schemas with GET /v2/data-connectors/format. The same four requests are declared in the webhooks section of openapi.json as connector.manifest, connector.accounts.search, connector.accounts.retrieve and connector.views.read, with the same schemas as components (ConnectorManifest, ConnectorAccountSearch, ConnectorAccount, ConnectorView), so the SDK generates the types of your answers.
| Route | Returns |
|---|---|
GET {base}/manifest | format: "lelemon-connector/v1", your name, how accounts are looked up and the views, each with a name, a description and an input schema written for a model. |
GET {base}/accounts/{id} | The card: sections of field, table, badge and action components. |
GET {base}/accounts?email= | A list of { id, title, subtitle? }. The email is only for searching: store the stable id as the externalRef of the contact. |
GET {base}/views/{viewId}?cursor=&limit=&name=value | { object: "list", columns, data, hasMore, nextCursor }. |
Rules the validator enforces:
- A field has a
valueType:text,number,money,date,statusorlink. Mark personal data withsensitive: true; mark health or other special category data withspecialCategory: true. - An
actionis an https link. Its host must be your base URL host or one of theallowedOriginsof the connector. Lelemon never calls it: the person opens it in a new tab. - Account and view ids match
[A-Za-z0-9._~-]{1,128}(view ids[a-z][a-z0-9_]{0,39}). The cursor is opaque and never a URL. - Unknown properties are dropped, never forwarded. A change that breaks the format ships as
lelemon-connector/v2; both are served. - Limits: 8 seconds per call, 1 MB per response, 20 sections, 50 components per section, 200 rows, 30 columns.
Build it with the SDK
The TypeScript SDK has types and helpers to build the four documents, a validator that reads the same JSON Schemas as GET /v2/data-connectors/format, and a lelemon command. The SDK installs as described in TypeScript SDK, and the lelemon command is described in Command line.
connector.manifest, connector.account, connector.field, connector.table, connector.badge, connector.action, connector.viewPage and connector.accountList fill the fixed parts (format, object, type, hasMore, nextCursor). validateConnectorDocument(type, document) returns the problems of a document with the same paths as the API; type is manifest, account, accountSearch or view. The SDK repository has a complete connector of about 50 lines that verifies the signature and serves these documents, at packages/sdk-ts/examples/connector.ts.
Check a file or a running connector from the terminal:
With a file, it detects which document it is (or take --type). With a URL, it reads /manifest, every view of the manifest and, with --account, that card. It prints ok or each failing path and reason, and exits 1 if anything fails. This is the schema check; the API also checks the hosts of the action links, so run POST /v2/data-connectors/format/validate once before you register.
To try a connector without writing a server, describe it in a fixture (manifest, accounts with an optional email to search by, and views by id) and serve it:
The fixture is validated when the command starts, and a broken one stops it with the failing paths. The server listens on 127.0.0.1 and does not check signatures unless you pass --secret <signing secret>. packages/sdk-ts/examples/fixture.json is a working one.
Verify that a call is from Lelemon
Every call carries Lelemon-Signature and the turn context headers. The signature covers the method, the host, the path and query and the actor, uses its own domain and expires in 60 seconds. Verify it with the SDK:
Read secrets with GET /v2/data-connectors/{id}/signing-secret (scope connectors:write). After a rotation the previous secret keeps signing for 24 hours: pass the whole list.
Check a document before you register
POST /v2/data-connectors/format/validate takes a manifest, an account card, an account search answer or a view page and answers every place that does not follow the format, with its path and reason. It runs the validator that checks the real answers of your system, stores nothing and calls nothing.
type is manifest, account, accounts_search or view_page. For account, send baseUrl or allowedOrigins so the hosts of the action links can be checked. POST /v2/data-connectors/{id}/check does the same for the manifest that your registered system really serves.
Register and check it
A connector is this object:
DataConnectorobject, 13 fields
objectenumrequireddata_connectoridstringrequirednamestringrequiredslugstringrequiredThe prefix of the copilot tools of this connector, for example backoffice_account.
originstringrequiredOnly the origin of the base URL. The full URL never comes back.
allowedOriginsstring[]requiredformatenumrequiredlelemon-connector/v1statusenumrequiredpending: created, never checked. active: the last check passed. error: the last check or the manifest failed, see lastError.
pendingactiveerrorlastErrorenumrequiredunavailableinvalid_responsenot_foundbad_requestnullviewCountintegerrequiredViews in the stored manifest.
manifestFetchedAtstring or null (date-time)requiredcreatedAtstring (date-time)requiredupdatedAtstring (date-time)required
The base URL must be https, public and inside the allowed sites of your key. An http URL, one with a user or password, or one that resolves to a private address answers 422 action_destination_blocked and nothing is saved. The connector starts pending. The check calls your manifest, validates it and, if it passes, makes the connector active. A manifest that does not follow the format answers ok: false with the failing paths, never the values. The check reads the manifest only. An account card, a search answer or a view page is validated when it is read: a read whose answer does not follow the format answers 422 data_connector_invalid_response, and its detail names the failing paths and the rule each one breaks, never the values. Test those documents before serving them with POST /v2/data-connectors/format/validate.
Masked by default
Fields marked sensitive, and any value shaped like a RUT, email, phone, card or secret, come back with value: null, masked: true and a maskReason. The same happens in titles, labels and table cells. The detection by shape is a fallback: mark your sensitive data yourself.
To see one value, call POST /v2/data-connectors/{id}/accounts/{accountId}/reveal with a key that has connectors:reveal. Fields marked specialCategory cannot be revealed.
In the contact page
A contact whose externalRef is the id of an account shows that account in its page of the dashboard, read live from each active connector with the same masking and the same reveal as above. It is for owners and admins, nothing is stored and the reads are recorded in the audit. When your system fails, the page says why and the rest of the contact keeps working. See Contacts.
Audit
Every read, reveal and action click is recorded with the key or member, the connector, the view and the field. The account is stored as a keyed hash and no value is ever stored. Rows are signed, kept for two years and listed with GET /v2/data-connectors/{id}/accesses (scope connectors:audit-read); a row that no longer matches its signature shows integrity: "tampered".
The copilot
An active connector gives agents of the internal kind three kinds of tools: find an account by email, read a card and read each view. Create the copilot with POST /v2/agents and audience: "internal", which needs a key with agents:internal and cannot come from a template. Then list its tools with GET /v2/agents/{id}/tools and grant them with PUT /v2/agents/{id}/tools/{toolId} (ids start with dct_). Open a case with it by sending its agentId and the pipelineId or stageId where the case lives; a copilot belongs to no stage, so the case needs a place of its own. Write to the case with author: "contact" and the copilot answers on the case like any agent, with the same messages and events, and the case shows in the dashboard. The copilot answers the people of your team, so it cannot open email cases and it cannot be linked to a stage. The assistant of the owner is not reachable by the API, and a copilot of another organization answers 404.
Once a turn has read data of your connector, the copilot gets no tool that writes or that calls another system for the rest of that turn: it can keep reading from the connector and answer. A tool it asks for anyway is not run and the model is told why. This is the defense against an instruction hidden in your data; the next turn has all its tools again. They are marked noPersist: the result lives only in the turn, and traces keep only status and duration. The content does pass through the prompt to the model provider, so do not mark as non-sensitive what must not leave your system.