Version 1. Keys are issued per customer by Beeylo.

Beeylo API

Use Beeylo's reply technique from inside your own CRM or mail client. Beeylo reads the connected mailbox, works out which decisions an incoming mail needs from the business owner, and turns the owner's answers into a reply in the owner's own words.

Beeylo never sends anything through this API.Your system shows the reply, the user reviews and edits it, and your system sends it.
BASEhttps://business-api.beeylo.com

Before you start

  • The user has a normal Beeylo account, and the same mailbox your system syncs (for example Gmail or Google Workspace through Unipile) is connected to Beeylo the usual way. Beeylo then reads it, including the Sent folder, so it keeps learning the user's style.
  • The user can keep working in the Beeylo app alongside your system. Handling a mail there works as usual: Beeylo may archive it or mark it as read in Gmail, and your system sees that like a change from any other mail client.
  • Optional read-only mode, set by Beeylo on request: Beeylo then never archives, labels, marks as read, moves, deletes, drafts in or sends from the mailbox. Sending from the Beeylo app is then off.
  • Prices, policies, writing style and settings are managed in the Beeylo app, not through this API.
  • You receive an API key from Beeylo, one per integration. Ask for a new one to rotate it.

Machine-readable versions of this page: openapi.yaml (OpenAPI 3) and api.md (Markdown, handy for AI coding assistants).

Authentication

Send the key as a bearer token with every request.

Authorization: Bearer bk_live_<43 characters>
  • The key acts as the Beeylo user it was issued for, with exactly that user's access: their own mailboxes and the shared mailboxes they may use.
  • Keep the key on your server. Never put it in browser code or a mobile app. Browser (CORS) calls to /v1/ are refused.
  • An unknown or revoked key returns 401. After 30 wrong keys from one IP address in an hour, that address gets 429 for the rest of the hour.

Limits and timeouts

WhatLimit
Requests per key60 per minute. Above that 429 te_vaak with a Retry-After header in seconds. Only accepted requests count.
Writing per user per dayBeeylo's normal daily limit. When reached: 429 dagcap. Retrying the same day does not help.
Response timevoorstel, schrijven and aanpassen take 10 to 30 seconds. Use a client timeout of 90 seconds. The list and afgehandeld answer in well under a second.
Request body256 KB at most.

The flow

  1. GET /v1/mails
    List the mails that need a reply.
  2. POST /v1/mails/{id}/voorstel
    Get the questions for the owner, or a ready reply. Show the questions and collect the answers.
  3. POST /v1/mails/{id}/schrijven
    Turn the answers into a reply. Show it in an editable field and mark the sentences Beeylo is not sure about.
  4. POST /v1/mails/{id}/aanpassen
    Optional: "shorter", "more formal" on the current text.
  5. The user sends the reply from your system.
    Beeylo is not involved in sending.
  6. POST /v1/mails/{id}/afgehandeld
    Mark the mail as handled in Beeylo.
  • {id} is the Beeylo item id (a uuid) from the list. An unknown id, or one that belongs to someone else, always returns 404 zaak_onbekend.
  • JSON keys follow the Beeylo app and are Dutch (voorstel, twijfelzinnen). Each one is explained below.
  • Unknown fields in a request are rejected with 400, so typos never go unnoticed.

Matching with your own copy of a mail

The list returns message_id: the RFC 822 Message-ID header of the newest incoming mail of that item, with angle brackets. Match it with the Message-ID of your own copy (in Unipile: message_id or the headers).

Beeylo does not store Gmail message or thread ids. When you send the reply, set In-Reply-To to that message_id so it lands in the same thread.

List open mails

GET/v1/mails?limiet=50&voor=<timestamp>

Mails that need a reply, newest activity first. Same selection as the Beeylo app: no newsletters, notifications or deleted items.

QueryMeaning
limiet1 to 100, default 50.
voorOnly items with laatste_activiteit before this ISO timestamp. Pass volgende from the previous page.
Response 200
{
  "mails": [
    {
      "id": "6a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
      "titel": "Cake for Saturday",
      "samenvatting": "Sanne wants to pick up a cake on Saturday.",
      "categorie": "question",
      "mailbox": "[email protected]",
      "onderwerp": "Cake for Saturday",
      "van_naam": "Sanne de Vries",
      "van_adres": "[email protected]",
      "ontvangen_op": "2026-10-28T09:12:44+00:00",
      "message_id": "<[email protected]>",
      "laatste_activiteit": "2026-10-28T09:12:50+00:00"
    }
  ],
  "volgende": null
}

volgende is null on the last page. message_id, onderwerp, van_* and ontvangen_op can be null for very old items.

Get the questions

POST/v1/mails/{id}/voorstel

Body is optional: { "taal": "en" } sets the language of the questions (nl or en), used only when Beeylo has to make a new proposal. Usually the questions were already made when the mail arrived, and then the answer is instant and free. Use the taal from the response in the next calls.

Response 200
{
  "taal": "en",
  "voorstel": {
    "vraag_id": "e0000000-0000-4000-8000-000000000001",
    "modus": "niets",
    "aanhef": "Hi Sanne,",
    "groet": "Best,\nNoor",
    "klant_taal": "en",
    "tekst": null,
    "stukken": [],
    "aan_jou": ["Saturday pickup", "Which cake"],
    "beslissingen": [
      {
        "id": "b1",
        "label": "Saturday pickup",
        "vraag": "Can she pick up the cake on Saturday?",
        "soort": "ja_nee",
        "opties": [],
        "alleen_zaterdag": false,
        "citaat": "could I pick up a cake on Saturday?"
      },
      {
        "id": "b2",
        "label": "Which cake",
        "vraag": "Which cake do you offer her?",
        "soort": "keuze",
        "opties": ["Apple pie", "Chocolate cake"],
        "alleen_zaterdag": false,
        "citaat": null,
        "uitleg": { "Apple pie": "Mentions the price from your knowledge" }
      }
    ]
  }
}

Showing the questions

Show beslissingen in order, each with vraag. Use label as a short title and citaat as the quote from the customer's mail when present. The input depends on soort:

soortInputSend back as antwoord
ja_neeYes / No"Yes" or "No", in the language of the question ("Ja" / "Nee" in Dutch).
keuzeOne of optiesThe option text, exactly.
datumA dateThe date as text. alleen_zaterdag: true means only Saturdays make sense.
tekstFree textWhat the user typed.
  • uitleg (optional): a short line per option, to show under the option.
  • detail (optional, on ja_nee): "wanneer" or "hoeveel". After a Yes, ask for the moment or the amount.
  • vervolg.opties (optional, on one question): next steps to pick after a Yes, such as "Send a quote".
  • modus: "volledig": Beeylo already wrote a full reply in tekst and there are no questions. Show the text; you do not need schrijven.
  • aanhef and groet are the greeting and sign-off. Send them back unchanged or as edited by the user.
  • vraag_id identifies the customer mail this proposal answers. If a newer mail arrives in the meantime, schrijven answers 409 nieuwe_mail: call voorstel again.
  • Other fields (koppen, zelf_melden, een_voor_een, tijden) are hints for a richer screen. You can ignore them in a first version.

Write the reply

POST/v1/mails/{id}/schrijven
Request
{
  "vraag_id": "e0000000-0000-4000-8000-000000000001",
  "taal": "en",
  "aanhef": "Hi Sanne,",
  "groet": "Best,\nNoor",
  "antwoorden": [
    { "id": "b1", "vraag": "Can she pick up the cake on Saturday?", "antwoord": "Yes", "soort": "ja_nee" },
    { "id": "b2", "vraag": "Which cake do you offer her?", "antwoord": "Apple pie", "soort": "keuze",
      "toelichting": "With extra cinnamon" }
  ],
  "overgeslagen": [],
  "aanwijzingen": []
}
FieldRequiredMeaning
vraag_idyesFrom the proposal.
antwoordenyes*One per answered question: id, vraag, antwoord. Optional: soort, toelichting (a note from the owner), label, citaat, nummers, vervolg, detail.
taalnonl (default) or en, as in the proposal.
antwoord_taalnoTwo-letter code to force the reply language. Default: the customer's language.
aanhef, groetnoGreeting and sign-off.
overgeslagennoThe labels of questions the owner skipped.
aanwijzingennoExtra instructions: [{ "vraag": "...", "tekst": "..." }].
vervolgnoThe next step picked above a full reply.
maak_mailnoAfter stijf: true: one more attempt at a natural mail.

* antwoorden may be empty if aanwijzingen or vervolg is present. detail is { "soort": "wanneer" | "hoeveel", "tekst": "Friday" }. antwoord is always the text the owner chose, never an index.

Response 200
{
  "klant_taal": "en",
  "tekst": "Hi Sanne,\n\nSaturday works. The apple pie with extra cinnamon will be ready for you.\nIt costs 30 euros.\n\nBest,\nNoor",
  "twijfels": [],
  "twijfelzinnen": [{ "zin": "It costs 30 euros." }],
  "stijf": false,
  "ontbreekt": [],
  "herkomst": [{ "zin": "Saturday works.", "bron": "jij" }],
  "bijlage_voorstel": false
}

Sentences to check: twijfelzinnen

These sentences contain something the owner did not provide. Beeylo never fills that in silently. Always mark these sentences in your UI and let the user confirm, edit or remove each one before sending. Find each zin by its exact text inside tekst.

Hi Sanne, Saturday works. The apple pie with extra cinnamon will be ready for you. It costs 30 euros.
  • tekst: the reply. Put it in an editable field.
  • twijfels: other review points in plain language, for example a skipped question. Show them near the text.
  • stijf: true: a literal, stiff version, because the natural one did not pass Beeylo's checks. Offer a "make it a mail" button that calls schrijven again with "maak_mail": true.
  • ontbreekt: answers that did not make it into the reply, as [{ "id", "vraag", "antwoord", "melding" }].
  • herkomst (optional): where each sentence came from: jij (the owner), kennis (the owner's knowledge) or beeylo (written by Beeylo; beleefd: true means only a polite filler).
  • bijlage_voorstel (optional): the reply mentions an attachment. Prompt the user to add one.

Adjust the text

POST/v1/mails/{id}/aanpassen
Request
{
  "concept": "Hi Sanne,\n\nSaturday works. The apple pie will be ready.\n\nBest,\nNoor",
  "opdracht": "shorter",
  "taal": "en",
  "vraag_id": "e0000000-0000-4000-8000-000000000001",
  "sleutel": "6a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d"
}
  • concept (the current text) and opdracht (free text: "shorter", "more formal", "mention we are open until 17:00 on Saturday") are required.
  • sleutel is a uuid you generate. A retry after a timeout with the same sleutel returns the first result instead of counting twice.
  • With vraag_id, a newer customer mail gives 409 nieuwe_mail.

Response: { "tekst": "...", "twijfels": [], "twijfelzinnen": [{ "zin": "..." }] }. Handle twijfelzinnen exactly as in schrijven.

Mark as handled

POST/v1/mails/{id}/afgehandeld

Empty body {}. Moves the item to Done in Beeylo, as ticking it in the app does. Call it after sending, so you do not have to wait until Beeylo sees the sent mail in the Sent folder. Calling it twice is fine. Beeylo then handles the mail in Gmail as the app does (for example archiving it); in read-only mode nothing changes in the mailbox.

Response 200
{ "ok": true, "id": "6a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d", "status": "afgehandeld" }

Errors

Every error looks like { "error": "<code>", "message": "<short text>" }. Validation errors also list the field names (never their contents) in velden.

StatuserrorMeaning
400ongeldige_invoerThe request does not match the schema. See velden.
400ongeldige_jsonThe body is not valid JSON.
401sleutel_ontbreekt, ongeldige_sleutelNo key, or an unknown or revoked key.
404zaak_onbekendUnknown item, or not yours.
404niet_gevondenUnknown address.
405methode_niet_toegestaanWrong HTTP method. See the Allow header.
409nieuwe_mailA newer customer mail arrived. Call voorstel again.
409geen_antwoordThis mail is not answered by mail (no-reply address, portal).
409geen_tekstThe original mail is gone, so no reply can be made.
413te_grootThe request is too large.
429te_vaakRate limit. Wait Retry-After seconds.
429dagcapThe user's daily writing limit is reached.
500serverfout, onbekendSomething went wrong at Beeylo. Retry later.
503model_wegThe language model is unavailable. Retry in a minute.
504te_traagA service behind Beeylo was too slow. Retry.

Rules you agree to

  1. Never send a reply automatically. A person reviews every reply before it is sent.
  2. Always show the sentences Beeylo is not sure about (twijfelzinnen) and the review points (twijfels), and let the user confirm or change them.
  3. Do not use Beeylo's output to train or improve any model, your own or a third party's. Mail and replies are customer data: do not log them in plain text or share them beyond the screen that shows them to the user.
  4. Keep the key secret and on your server. Tell Beeylo at once if it may have leaked.
  5. Do not work around the limits. Ask Beeylo if you need more.

Full example

bash
KEY=bk_live_...
API=https://business-api.beeylo.com

# 1. open mails
curl -s "$API/v1/mails" -H "Authorization: Bearer $KEY"

# 2. questions
curl -s -X POST "$API/v1/mails/$ID/voorstel" -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" -d '{"taal":"en"}' --max-time 90

# 3. write the reply
curl -s -X POST "$API/v1/mails/$ID/schrijven" -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" --max-time 90 \
  -d '{"vraag_id":"...","taal":"en","antwoorden":[{"id":"b1","vraag":"...","antwoord":"Yes","soort":"ja_nee"}]}'

# 4. after the user sent it from your system
curl -s -X POST "$API/v1/mails/$ID/afgehandeld" -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" -d '{}'

Building with an AI coding assistant such as Claude Code? Give it the links to api.md and openapi.yaml; together they describe every field and error.