Lelemon
  • Get started
  • Use cases
  • Guides
  • API basics
  • Webhooks
  • SDK, CLI and MCP
  • API reference
  • Changelog
  • v1 (legacy)
Configure your account
Talk to people
Run and protect
    Give an agent a jobSchedule a jobTrack usage and cap spendingRun Lelemon for your customersKeep tool data out of storageErase a contact or a messageBest practicesMoving from v1
powered by Zudoku
Run and protect

Erase a contact or a message

Two operations remove personal data. Erase a contact when a person asks to be forgotten or when you decide it as the data controller. Redact a message when one line of a thread holds something that must not stay, such as an ID number or a password.

Both are permanent. There is no undo and no soft delete.

You will

Preview an erasure, erase a contact, follow the job until it ends and redact a message.

You need

  • A key with contacts:erase and contacts:read, issued by an admin or the owner
  • A webhook endpoint subscribed to contact.suppressed
Your backendLelemon APIErasure jobbackground batchesDELETE /v2/contacts/{id}?dryRun=true1status: previewinventory, counters only2DELETE /v2/contacts/{id}reason, closeOpenCases3202 suppressionthe contact is a 404 now4queued5inventory, rows, objects, restthe phases, in order6GET /v2/suppressions/{id}7contact.suppressedids only8
Preview first, then erase. The call answers 202 and the contact is gone for readers at once. The job runs in phases and ends with a webhook that has only ids.
  1. Send dryRun=true first. It changes nothing.
  2. The answer is a suppression with status preview and an inventory per store. Never content.
  3. The real call. It is permanent: there is no undo and no soft delete.
  4. Accepted, not finished. The contact answers 404 from this moment, so nobody can read it while the job runs.
  5. The job runs in the background in short batches and can resume if a process dies.
  6. status goes queued, running, done. phase says where it is: inventory, rows, objects, rest.
  7. Follow the job whenever you want. counts holds numbers only.
  8. When the job ends the webhook contact.suppressed arrives with the ids and nothing else. It is your signal to erase the person from your own systems.

Preview what would be erased

Send dryRun=true first. It changes nothing and returns counters per store, never content.

The answer is a suppression with status: "preview" and an inventory: cases, open cases, files and the rows per store.

Erase a contact

The call answers 202 with a suppression. The contact answers 404 from that moment, so nobody can read it while the job runs.

The suppression is this object. Open it to see every field:

Suppressionobject, 14 fields
  • objectenumrequired

    suppression

  • idstring or nullrequired

    null on a dryRun: nothing was created.

    Example sup_3PqA0x8bVw1TqS9mYp3RkL

  • contactIdstringrequired

    Example ctc_7Hc2LmQ0x8bVw1TqS9mYp3

  • statusenumrequired

    queuedrunningdonefailedheldpreview

  • phaseenumrequired

    inventoryrowsobjectsrestdonenull

  • reasonenumrequired

    data_subject_requestcontroller_instruction

  • legalHoldbooleanrequired
  • closedOpenCasesnumberrequired
  • countsmap of integerrequired

    Rows erased or anonymized per store. Numbers only.

  • errorCodestring or nullrequired
  • inventorySuppressionInventory or nullrequired

    Only on dryRun: what would be erased, by store, without content.

    SuppressionInventory4 fields
    • casesnumberrequired
    • openCasesnumberrequired

      Open cases. While there are any, the real call needs closeOpenCases=true.

    • filesnumberrequired
    • storesSuppressionInventoryStore[]required
      SuppressionInventoryStore3 fields
      • idstringrequired

        Example conversation_messages

      • storeenumrequired

        tableobject-store

      • rowsnumberrequired
  • createdAtstring or nullrequired
  • completedAtstring or nullrequired
  • dryRunbooleanoptional

    Present and true only on a dry run: nothing was erased and the inventory shows what would be.

ParameterMeaning
reasondata_subject_request when the person asked, controller_instruction when you decided. Kept in the suppression.
closeOpenCasesWithout it, a contact with open cases is a 409 resource_in_use. With it, the open cases are closed and erased.
legalHoldtrue when you must keep the data by law, for example a debt, a credit or a refund. The request is recorded as held and nothing is erased. Repeat the call without legalHold when the obligation ends.
dryRuntrue returns the inventory and changes nothing.

What is erased

The contact is anonymized in place, not deleted, so your statistics keep working. Its cases, messages, files, fields, agent memory, logs and queued jobs lose their content.

What remains of each case is an aggregate shell. It has a new id with no table that maps it to the old one, no contact, and dates cut to the day. It is aggregate data, not anonymous data: do not present it as anonymous.

Files are deleted from storage with all their versions and replicas.

The responses stored for Idempotency-Key are erased too. A retry with the key of a request that described the person answers 409 idempotency_key_reused: send it again with a new key. The queues that hold the person's messages (webhook deliveries, sends, incoming messages, WhatsApp history and pipeline jobs) are purged of their jobs.

Opt-outs are the exception. The contact is anonymized but its opt-out stays, with a keyed hash of the phone and the email instead of the values, so nobody writes to that number again.

What waited for a person about the contact is closed first: approvals that were pending or approved become canceled (event approval.canceled) and lose their arguments, and the team questions still open are discarded. They never run and nobody is told. See Approve agent actions.

Follow the job

The erasure runs in the background in short batches and can resume if a process dies.

status goes queued, running, done. phase says where it is: inventory, rows, objects, rest. counts holds numbers only. When it ends, the webhook contact.suppressed arrives with the ids and nothing else.

Deadlines

WhereWhen
Our database and file storageWhen the job ends
Our processors (WhatsApp provider, Meta, logging)Within 30 days
BackupsWhen each backup expires. If we restore one, we erase the person again from the list of suppressions

Your own systems are yours. The data processing agreement obliges you to erase the person wherever you copied them. The contact.suppressed webhook is the signal to do it.

Redact a message

The text becomes [redacted]. The file, the transcription and the copies of the message in logs, events and agent memory are erased. The rest of the thread stays.

It is idempotent. Redacting a redacted message answers 200 with the same message and publishes nothing.

The webhook message.redacted carries the case id and the message id. The webhooks you already delivered with message.created carried the text, so redact the message in your own systems when it arrives.

Agents and the API key

The MCP server of the API does not offer erasure by default. Give an agent the key only when a person approves each erasure.

Keep tool data out of storageBest practices
On this page
  • Preview what would be erased
  • Erase a contact
    • What is erased
    • Follow the job
    • Deadlines
  • Redact a message
  • Agents and the API key
curl -X DELETE "https://api.lelemon.cl/v2/contacts/ctc_7Hc2LmQ0x8bVw1TqS9mYp3?dryRun=true" \ -H "Authorization: Bearer $LELEMON_API_KEY"
curl -X DELETE "https://api.lelemon.cl/v2/contacts/ctc_7Hc2LmQ0x8bVw1TqS9mYp3?reason=data_subject_request&closeOpenCases=true" \ -H "Authorization: Bearer $LELEMON_API_KEY"
curl "https://api.lelemon.cl/v2/suppressions/sup_3PqA0x8bVw1TqS9mYp3RkL" \ -H "Authorization: Bearer $LELEMON_API_KEY"
curl -X POST "https://api.lelemon.cl/v2/cases/cse_7Hc2LmQ0x8bVw1TqS9mYp3/messages/msg_3PqA0x8bVw1TqS9mYp3RkL/redact" \ -H "Authorization: Bearer $LELEMON_API_KEY"