Call Intake so a channel adapter can turn one unstructured request into one commercetools entity. The HTTP surface is a single stateless endpoint. Each call starts a fresh run and returns either a created entity or an error. It does not resume a previous run, so a follow-up email or ticket comment is not another turn of the same call.
The adapter owns three decisions Intake will not make: which entity to create, which Customer the message is for, and whether this inbound event is a new request or a reply to an entity already created.
Out of scope
- Building or automating the Merchant Center Intake UI. Use that UI once, before writing the adapter, to confirm the same prompt creates the entity. The UI can ask a clarifying question. This endpoint cannot.
- Connect connectors, transactional email, and CRM sync. Those belong to commercetools-integrations.
- Quote negotiation after a Quote Request exists (Staged Quote, Quote, Order). That is the Quotes API.
Workflow
- Docs search (required, run first) — The first time you use this skill in a session you must run this before answering. It gathers the latest verified documentation as your primary grounding, filtered to the products this skill covers. Use this script for documentation search while working with this skill; the Knowledge MCP covers everything else. Always confirm details against retrieved documentation rather than the skill text alone:
node "<this skill's directory>/scripts/docs-search.mjs" \
--query "Intake responses API Cart Quote Request Shopping List file upload" \
--app-name "<host app: claude-code, claude-chat, cursor, codex, copilot — or the host's own name>" \
--model "<current-model>" \
--commercetools-project-key "<if known>" \
--commercetools-region "<if known>" \
--limit 10
Pass the commercetools project key and region (as in api.{region}.commercetools.com) only if already in your context; otherwise omit both. Never search files or ask the user for them.
-
Fetch the REST schema before writing a request or parsing a response. Run:
node scripts/openApi-schemata.mjs \ --resource-name "api-AgentResponsesSuccess" \ --app-name "<host app: claude-code, claude-chat, cursor, codex, copilot — or the host's own name>" \ --model "<current-model>"The output is the OpenAPI specification for that resource. If the name is not recognized, the script prints valid names. Re-run with
api-Cart-write,api-QuoteRequest, orapi-ShoppingListwhen the follow-up is an update to the entity Intake created, not another Intake call. -
Confirm the Project can accept the call. Intake is enabled for the Project (otherwise
FeatureDisabled). The API client has themanage_intake_agent:{projectKey}scope. Product Search is enabled (otherwiseProductSearchNotEnabled). The buyer already exists as a Customer. Intake looks a Customer up by email and does not create one. -
Run the same prompt in Merchant Center before writing the adapter. Paste the prompt and any file into Merchant Center Intake. A failure there is the Project, catalog, or Customer. A success there and a failure from the API is the request the integration sent. A success that needed a clarifying question does not count: the prompt that produced the entity is the prompt the API body must contain.
-
Choose
outputType. Read choose-output.md. The field is required. Intake never infers it from the message. -
Map the channel into one request. Read call-responses.md, then the channel file:
- Mailbox: email.md
- Zendesk: zendesk.md
- Any other drop (a form, a drive folder, another helpdesk): the same mapping in call-responses.md. A new channel is a new normalizer, not a new Intake contract.
-
Call once per new request, and remember that you did. The endpoint has no idempotency key. Conditionally insert the channel's own id (email
Message-ID, Zendesk ticket id) with the normalized request as queued work, acknowledge the delivery, then mark the row as a call with an unknown outcome immediately before the POST. On success, store that id againstthreadId,entity.id, and, for a Quote Request,cartId. Recovery may send a queued row that was never marked. It must not resend a marked row. A second POST creates a second entity. -
Handle the outcome and write it back using handle-outcome.md. A
201can still carry warnings. A400names what was missing. Do not answer a400by calling again with the same body. -
Route a later message on the same thread by the stored outcome. An entity already stored is updated with the normal Cart, Quote Request, or Shopping List APIs. A confirmed no-write failure (a
400that created nothing) allows one corrected Intake attempt when the reply supplies the missing input. A pending queued row, a call with an unknown outcome, or a partial creation (QuoteRequestCreationFailedwithcartId) is resolved from that stored state. Do not POST again to finish it.
Checklist
- The same prompt created the entity in Merchant Center before the adapter was written. A clarifying question in that UI does not count
-
outputTypewas chosen by the integration, not left for Intake to guess - The prompt states the buyer's email, and for a Cart or Quote Request a shipping country, in plain text
- A Shopping List name is stated in the prompt or a file. Intake does not invent one
-
businessUnitandstoreare passed as resource identifiers when the channel knows them - Uploads use the
filespart, at most 3 files, only allowed content types - The channel event id and the normalized request are stored as queued work before the webhook is acknowledged. A call is marked as an unknown outcome immediately before the POST, then stored against
entity.idandthreadId. Recovery sends unstarted work and does not resend a call that may have completed - Warnings and the specific
400code are written back to the channel - A follow-up on the same email thread or ticket follows the stored outcome: update an existing entity, allow one corrected Intake attempt after a confirmed no-write failure when the reply supplies the missing input, and resolve a pending, unknown, or partial-creation outcome without a blind retry