Writing help

.md

Have a draft proofread, improved, or written to an instruction, with the conversation it answers taken into account.

Scopesmail:write
POST/v1/me/mail/assist

The writing help behind the spark in the web composer. Send the message as it stands and say what to do with it; you get back a proposal for the whole body and a few remarks for the writer. Nothing is saved. Show the proposal, and only when the writer accepts it put it in the composer and save the draft the way you always do.

What makes it worth calling rather than a spell checker is that it reads the conversation the draft answers. Name the message being answered in in_reply_to and the exchange around it is loaded on our side, from what this account may read, so a remark can say that the amount in the answer is not the amount the customer asked about. Never send the conversation yourself: it is not read from the request.

Every call is paid for out of the environment's AI allowance and appears in its AI usage alongside the calls made from the browser. That is why it takes mail:write although it changes nothing.

Whether to offer it at all is meta.writing_help on the signatures, which a composer asks for anyway.

Three actions

actionWhat it does
proofreadSpelling, grammar and punctuation only. The wording, the structure and the tone stay the writer's
improveThe same corrections, and wording that reads clearly and professionally. Same meaning, roughly the same length
customWhatever instruction asks. With an empty body it writes the message from the conversation

The proposal is in the language of the draft, or of the conversation when there is no draft yet. The remarks are in English.

Request

curl https://api.proppertrading.com/v1/me/mail/assist \
  -H "Authorization: Bearer ptat_9f2c1a44_3b8e7d2f5c9a1b4e6d8f0a2c4e6b8d1f" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2a8e-4b7d-4e19-9a52-0d3b8c7e5f21" \
  -d '{
    "address_id": "mbx_w6sy3skvgydh5e3b",
    "action": "improve",
    "subject": "Re: Account locked after the news spike",
    "body": "<p>hi, we looked at it and the drawdown was 5.2% so the breach is correct, sorry</p>",
    "in_reply_to": "msg_h3k8q2w7n4r9t5vx"
  }'
ParameterTypeDescription
address_idstringRequired. The id of the mailbox the message is written from, from list mailboxes
actionstringRequired. proofread, improve or custom
instructionstringRequired for custom, at most 1,000 characters. What to do, in the writer's words
bodystringHTML, as you would send it, without the quote and without the sign-off. Required unless action is custom. At most 100,000 characters
subjectstringThe subject as it stands. Optional, at most 255 characters
in_reply_tostringThe id of the message being answered. Its conversation is what the help reads
quotedstringHTML of a quoted message, for a forward or anything else without an in_reply_to. Read only when in_reply_to is absent

Response

{
    "data": {
        "object": "mail_suggestion",
        "body": "<p>Hi, we looked into it, and the drawdown was 5.2%, so the breach is correct. Sorry about that.</p>",
        "remarks": [
            "The draft gives a 5.2% drawdown, while Daan says the dashboard shows 4.2% and the email says 5.1%; please verify the figure before sending."
        ]
    },
    "meta": {
        "request_id": "req_01m48qq03h3zc7n1w4c0kb6ty7"
    }
}
FieldTypeDescription
bodystringThe proposed body as HTML, through the same sanitiser as anything typed into a composer. It replaces the whole body, not a part of it
remarksstring[]At most five, one sentence each, for the writer rather than the recipient: a figure that does not match, a question left unanswered. Often empty

A proofread of a draft with nothing wrong in it comes back with the same text. Compare the two as text rather than as markup before offering to replace anything, because the sanitiser may tidy markup it did not change a word of.

Retrying with the same Idempotency-Key returns the same proposal without a second call to the model, and without a second charge. Asking again on purpose needs a new key.

Limits

Fifteen calls a minute per person, shared with the web composer, and each one usually takes a few seconds. On top of that the environment's AI allowance applies, per window and per week, as it does everywhere else AI is used.

Errors

StatusCodeMeaning
403insufficient_scopeThis device does not hold mail:write
403mailbox_not_permittedThis account may not open a mailbox
403assist_unavailableThe environment has no AI: none is connected, or it is switched off. Waiting does not change it
404resource_not_foundaddress_id or in_reply_to is not in a mailbox this account can read
422validation_failedA field is missing or too long, or an id was sent as a number. instruction is required for custom, body for the other two
429assist_limit_reachedThe AI allowance is used up. state says which: session, week, none (no AI in the package) or platform (our capacity). resets_at says when it returns, absent when waiting will not help
429rate_limit_exceededMore than fifteen calls in a minute
503assist_failedThe model did not answer. Nothing changed; try again
{
    "error": {
        "type": "rate_limit_error",
        "code": "assist_limit_reached",
        "message": "The AI allowance for this week is used up. It comes back in 2 days.",
        "doc_url": "https://proppertrading.com/docs/api/errors#assist_limit_reached",
        "state": "week",
        "resets_at": "2026-10-09T08:00:00+00:00",
        "request_id": "req_01m48qnaktx6etz94ddyhx4v02"
    }
}