Generate message
generate_messageRegisters one message generation and returns immediately with 202 Accepted. Poll the
status resource named in Location (or honour Retry-After) until status is terminal;
the finished message is embedded in that resource. The API composes text; it does not
send it, and it does not replace the recipient's WhatsApp opt-in.
What the message may say
Every candidate fact about the recipient and the sender carries a tier. The message
asserts only facts of tier verified. reported and estimated facts may influence
which angle is chosen but are never stated as fact; unknown facts are never mentioned.
The tier of each field is derived from the provenance the record already carries
(field by field, from its source and verification evidence), not chosen by the model.
Personal data - contacts, officers, phone numbers, e-mail addresses, social handles, and,
for a sole trader, the person's own name and address - is never asserted. After
generation, code re-checks that each claim in the text maps to fact_ids of verified
facts; an unsupported claim is removed, or the request fails - it is never shipped.
Language
The message is written in the language of the recipient company's website main page.
There is no caller override. The decision is reported on the result as language
(ISO 639-1) and language_source. If the homepage language cannot be determined the
request fails with error code language_undetermined and the credits are returned;
the language is never guessed.
Unknown companies
If the recipient or sender company is not yet known, the status passes through
enriching, which runs the existing discovery/validation pipeline for that single
domain, then composing.
Cost and balance
There is exactly one debit, at POST, for the whole generation including any
enrichment. Enrichment never debits again and never changes the balance mid-job; a
generation that ends failed for any reason below, or canceled before composition
starts, returns that one debit. 402 insufficient_credits is therefore only ever
returned by POST.
Jurisdiction
Checked at POST against what is already known: 422 jurisdiction_excluded. A company
whose jurisdiction only becomes known after enrichment is excluded late: the
generation ends failed with error.error = jurisdiction_excluded and the credits are
returned. The text is never composed for an excluded jurisdiction.
Validation done before 202
Invalid body or hostname: 400 invalid_request. sender.company_domain is the sender's own
company domain: the service reads the sender's facts from the knowledge base by it and does not
verify that the caller controls it. When sender or sender.company_domain is omitted, the caller's
seller profile website is the sender; with no profile website either, the request is 400 invalid_request
(sender.company_domain is required when no seller profile website is set).
Recipient or sender in a policy-excluded
jurisdiction: 422 jurisdiction_excluded. Insufficient credits: 402 insufficient_credits. constraints.max_characters that no message can meet: 422 max_characters_unmeetable; when this only shows during composition the generation ends
failed with the same code and the credits are returned. callback_url must be an https URL on a public host: a URL resolving to a
private, loopback, link-local, reserved, multicast or cloud-metadata address is rejected
with 422 unsafe_url (the same rule as odoo_url on the tenant Odoo endpoint).
The service being temporarily unable to accept work: 503 service_unavailable with
Retry-After; nothing is debited.
Error codes
Every error uses the surface's single Error shape {error, message, request_id}.
error is a snake_case code: invalid_request, missing_idempotency_key,
idempotency_key_reused, request_in_flight,
jurisdiction_excluded, insufficient_credits, unsafe_url, max_characters_unmeetable,
not_cancelable, generation_expired,
service_unavailable, and, on a failed generation only, the MessageFailureCode values:
insufficient_verified_facts (too few verified facts exist to write anything true),
enrichment_failed, language_undetermined, language_failed, claim_verification_failed,
jurisdiction_excluded, max_characters_unmeetable, compose_failed, compose_timeout and
timed_out. A failed generation always returns its credits.
Idempotency (draft-ietf-httpapi-idempotency-key-header-07)
Idempotency-Key is required. Missing: 400 missing_idempotency_key. Same key with a
different payload: 422 idempotency_key_reused. Same key while the original request is
still being processed: 409 request_in_flight. Same key with the same payload: 200
(not 202) with the EXISTING resource in its current state (same id, no second debit).
A key is remembered for 24 hours from its first use; after that the same key starts a
new generation.
Tool call template
Replace each placeholder with a value matching the input schema. Only required parameters are included; add optional parameters as needed.
{
"id": 1,
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"arguments": {
"recipient": "<recipient>",
"channel": "<channel>",
"objective": "<objective>"
},
"name": "generate_message"
}
}Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| sender | No | The sender's own company domain. The service reads the sender's facts from the knowledge base by it; ownership is not verified. If sender is omitted, the caller's seller profile website is the sender; the profile's offering and description are used as sender facts. If sender.company_domain differs from the profile website, the profile is not used. | |
| channel | Yes | ||
| objective | Yes | What the message should achieve, in the caller's words. | |
| recipient | Yes | ||
| constraints | No | ||
| callback_url | No | RESERVED. Accepted and stored, but no callback is delivered yet; poll the status resource. Must be an `https` URL on a public host: private, loopback, link-local, reserved, multicast and cloud-metadata addresses are rejected with `422 unsafe_url` (same rule as `odoo_url` on the tenant Odoo endpoint). |