Skip to main content
Thirty-one of the 85 tools change something. Twenty-one of them write as soon as the agent calls them. Nine of them write nothing until you approve the change, in your browser or, for a contact update in Claude or ChatGPT, in an approval card beside the agent’s reply. One starts work in the Coffee Chats extension in your browser. After reading this page you will know which changes fall on which side, why the line is drawn there, what every write result looks like, and what happens between an agent’s proposal and the moment something is written.

The line between the two

A change is a safe write when it affects only your own account and you can set it back yourself from a screen in the app. A change is consequential when it alters what other people see, what they can book, or what reaches them by email. The seventeen safe writes cover your profile preferences, your job-search stage, adding contacts, importing contacts and jobs from a spreadsheet, the notes and tags on a contact, the notes and outcome on a meeting, a campaign’s name, description and goal, a campaign’s sending state, adding and editing internship applications, an internship application’s stage and priority, one part of a ready resume or cover letter, and the withdrawal of a review request you have not decided yet. Five of the nine that need your approval are about your booking page: its text, whether it is public, its scheduling rules, the details of one event type, and whether one event type is archived. The sixth sends a thank-you you already drafted, to the address stored on the contact linked to that meeting, at a time in the next forty-eight hours. The seventh, contacts.update, changes the details, status, rating, tags or notes of up to 25 of your contacts at once. It touches only your own records, but a status change can cancel a contact’s scheduled follow-ups or let automated sends reach that contact again, and one call can change many contacts, so it waits for you. The eighth, contacts.research.start, starts research on up to 25 of your contacts. Research spends credits, so it waits for you and is approved only in a browser, never in the approval card. The ninth, mail.outreach.update, changes your preferred name, email signature, scheduling link and email tone. Those go into every outreach email you send, so it waits for you. mail.drafts.send is one of the twenty-one that write immediately: it sends one outreach draft you already stored, and it cannot change the subject, the body, or the recipient. mail.batch.enqueue writes immediately too: it queues a table of your own outreach drafts, after each row is checked. documents.resumes.generate and documents.jobs.tailor write immediately too: they write a resume, or a tailored resume and cover letter, and use credits as soon as the agent calls them, so they need their own permission, documents:generate.

Why an idempotency key

Eighteen of the nineteen write tools described here take an idempotencyKey, a string of 16 to 128 characters that the agent chooses for one particular change. It is how Coffee Chats recognises a repeat of a call it has already seen.
  • Send the same tool, the same key and the same input again, and Coffee Chats returns the answer it already computed. Nothing is written twice. That is what makes a retry safe after a lost answer.
  • Send the same key with different input, and the call is refused with VERSION_CONFLICT. A key names one change, and it cannot be pointed at a different one.
  • A genuinely new change needs a new key. Reusing an old key to force a retry returns the old answer.
A stored change is made unique by the connection, the tool name and the key together, so the same key sent to two different tools names two different changes and neither can overwrite the other. The nineteenth write tool is actions.cancel, which withdraws a review request. Its whole input is operationId, and it takes no idempotency key at all, because the identifier of the review request it withdraws already names one thing. Sending it a key is refused before the tool runs. contacts.create and contacts.bulk_create are not among the nineteen. They take no key: a repeated call finds the contacts the first call added and reports each row as a duplicate with that contact’s id, so nobody is added twice. They answer with one outcome per row (created, duplicate or error) rather than the result object below. jobs.applications.create and jobs.applications.update are not among the nineteen either, and take no key. A repeated create finds the application the first call added and returns it with duplicate: true, and an update sets each field it names to the value sent, so sending it again changes nothing more. Both answer with the application’s id, its stage and whether the Coffee Chats extension can apply to it now, rather than the result object below. contacts.import and jobs.applications.import are not among the nineteen either, and take no key. A repeated import finds the rows the first call added and counts them as skipped, so nothing is added twice. They answer with counts of the rows created, updated, skipped and failed and the first ten row errors, rather than the result object below. The three document writes, documents.resumes.generate, documents.jobs.tailor and documents.section.update, are not among the nineteen either, and take no key. A repeated generate or tailor returns the ready document without a new charge unless the agent asks to regenerate it, and each section edit saves a new version. They answer with the document itself rather than the result object below. mail.drafts.send, mail.batch.enqueue and jobs.applications.apply are not among the nineteen either, and take no key. Each one’s page in the tool reference prints the result it answers with.

The result object

Every write tool answers with one structured object rather than free text. This section is the one description of that object, and every example on every page matches it. The object arrives under structuredContent in the tool response. The same JSON is repeated as text in the response’s content array, so an agent host that reads only text still sees it. A completed safe write looks like this in full:
Completed write result
The examples on the rest of these pages print the inner object on its own, because that is the part an agent branches on. Every one of them sits under structuredContent on the wire.

Read tag first, then status

A write result carries either a tag field or a status field. It never carries both. Branch on tag when it is there, and on status when it is not. Two contracts meet here, which is why there are two field names. The result format is the union of the contract for a tool call and the contract for reading the state of one change. The first is keyed on tag, the second on status, and a write tool answers on whichever of the two fits the state it is reporting.

Every field

scenario is one of eight values on a write result: success, loading, conflict, denied, expired, unavailable, cancelled and uncertain. The first two appear on results that have not failed. The other six appear on the three groups that status reports. The contract also declares empty, and only a contact update sends it: a completed contact update that changed no contact, because every contact it named had changed or been deleted since you were shown it.

The two identifiers

operationId names one change. On a consequential tool it is also the review request identifier, and it is the last part of the review link the tool hands back. To learn what you decided, the agent calls the same tool again and sends that value in the operationId field. There is no other field and no other address that carries it. correlationId sits inside the error object and identifies the same change in the Coffee Chats logs. On every result the connector produces today it carries the same value as operationId. Record it, and include it when reporting a problem to support. It means nothing else to your agent. Every Coffee Chats identifier is a 24-character hexadecimal string. The examples in these pages use a different one for each kind of record, so c0ffeec0ffeec0ffee000001 is always a contact and 0123456789abcdef01234567 is always an operation or a review request.

Shapes the contract allows and no tool sends

The result format is shared by tools that do not exist yet, so it declares more than any tool returns today. The requires_user_action tag and the pending, processing and succeeded values of status are declared and no tool an agent calls sends them. The approval card’s own tool, actions.decide, answers with requires_user_action when the card cannot decide a review request, and the card shows the review link. The requires_confirmation shape also declares an optional actionId field, and no tool sends it either, so operationId is the only identifier a review request ever arrives under. Do not build against a result shape you have not seen documented for a specific tool. Each tool’s page in the tool reference lists the shapes that tool can return. Report anything else as an unrecognised answer instead of acting on it.

What each outcome means

An accepted result and an uncertain result are easy to confuse. Accepted means the work is still running, so poll again. Uncertain means the work is over and Coffee Chats cannot say what it did, so polling will never resolve it.

How to poll

To poll a safe write, call the same tool again with the same idempotency key and the same input. The answer is the current state of that change. Nothing is written a second time. To poll a tool that waits for approval, call the same tool again and send only operationId, set to the value the first call returned. That form of the call takes no other property. contacts.update is the exception: poll it with contacts.update.get and the same operationId, because an application that shows the approval card would show a new card on every call to contacts.update. contacts.research.start takes no operationId: to learn how a research batch ended, call it again with the same contacts and the same idempotency key, and read each contact’s research with contacts.research.get. Every other consequential tool publishes one input object that names the properties of both forms, the one that proposes the change and the one that asks what happened to it, and every one of them is optional in the published schema. That is what an agent host requires, and it is not a licence to mix the two. Sending operationId beside the fields of a proposal is refused, and so is a proposal with a property from no form at all. Each tool’s page in the tool reference writes out the forms that tool accepts. In both cases, stop when the result is completed, failed, cancelled or uncertain, which are the four states nothing moves out of.
Poll at a human pace. The student has to open a link, read a page and press a button. The connector allows 120 agent calls a minute on one connection, and a tight polling loop spends that budget on one waiting change and locks the agent out of its own reads.

The flow of one approval

A tool that waits for approval never writes on its first call. It creates a review request, hands back a link, and waits. The agent finds out what happened by calling the same tool again with operationId, or contacts.update.get for a contact update, or contacts.research.start with the same idempotency key for research. An agent can never approve its own review request. An existing Coffee Chats session skips the sign-in step. An edit form exists only on booking page text, on scheduling rules and on event type details. The visibility and archive reviews have no edit form, because each of those carries a value with only two states. A review request waits thirty minutes from the moment it is made. After that it expires and nothing is written. Editing the values does not extend that deadline: an edit creates a new version of the same review request, under the same identifier, and that version still needs approving. For a contact update in Claude or ChatGPT, the approval card can stand in for the browser steps: you read the same changes beside the agent’s reply and press Approve or Deny there, and the review link stays on the card if you would rather use the page. Approvals describes the card, what the review page shows you and how each review request can end.

The retry hint

A write can be accepted and still ask the agent to come back later. When that happens the answer is an accepted result with a hint beside it, under _meta rather than inside the result. The hint holds a code, the fixed value true for retryable, and a delay in milliseconds. Nothing has gone wrong and nothing has been written yet. The two codes a hint can carry are PERMIT_BACKPRESSURE and PERMIT_CONTENTION, and both mean the same thing to a caller: wait for the stated delay and call again with the same idempotency key. The delay is at least 1 millisecond and at most 5,000, and every write tool that issues one sets it to 1,000.
Do not treat a retry hint as a failure. The change has been accepted and will proceed. If your agent stops here, it reports a failure for work that was going to succeed.

Next steps

Approvals

What the review page shows, and the nine states a review request can be in.

Update a preference safely

One safe write worked through, with its replay and its conflict.

Propose a booking page change

The agent’s side of one approval, end to end.

Error codes

Every code a write can return, its cause and the action to take.