Last updated: 2 October 2026
There are three ways to work with the Layer from outside:
- Connect an AI app via MCP, such as Claude or ChatGPT. You sign in to the Layer and choose what the app may do.
- The REST API for your own software, with a key you create yourself.
- Webhooks: the Layer notifies your address that something happened, such as a paid invoice.
1. How it stays safe
- Never more than the creator. A key or connected app can never do more than the person who created it. If their role changes, the key changes with it. If someone leaves the team, their key stops working.
- Writing is always a proposal. Nothing from outside changes anything in your workspace by itself. Every change goes to Approvals and only happens once someone with that permission approves it.
- Text from outside stays marked. What comes in via the API or MCP counts as text from outside. A proposal with such text never goes through automatically, even if you let other proposals run automatically.
- Visible and revocable. Under Settings, Devices you see every key and app, when it was last used and what it did. Revoking takes effect immediately.
- A brake. Each key and app may make 120 requests per minute. Above that you get a 429 with a Retry-After.
2. Creating a key
In the Layer: Settings, Developers, API key. Give the key a name, choose how long it is valid (30 days, 90 days or a year) and tick what it may do. You see the key once. We only store a hash, so lost is lost: revoke it and create a new one.
Keys belong to your own workspace, not to the demo. The exact address of your API and MCP server is on the same page; the examples below use https://jouw-werkplek.example.
| Permission | What it grants |
|---|---|
relaties.lezen | Customers, leads, suppliers and their contacts. |
offertes.lezen | Quotes with their lines and status. |
facturen.lezen | Invoices with amounts, status and payment date. |
leads.lezen | Leads with stage, value and next action. With Make proposals, also create and update leads. |
communicatie.lezen | The timeline of calls, emails, conversations and notes per relation. With Make proposals, also log them. |
mail.lezen | Search and read the mailboxes you see in the Layer yourself, with per conversation who wrote last and whether there is already a reply. Read only, without attachments. What the app reads goes to the AI you connect. |
agenda.lezen | Your own appointments. With Make proposals, also schedule an appointment, without an invitation. |
facturen.concept | Create a draft invoice (from open hours or with your own lines) or an open invoice with a link for the customer, edit it and view the PDF. Sending is always a proposal in Approvals; you share the link yourself. |
offertes.concept | A new quote as a draft. You always send it yourself in the Layer. |
bedrijf.vragen | Searches meeting notes, the knowledge base, customers, tasks and records for the pieces that match a question, with the source. |
projecten.lezen | Projects with status, deadline and tasks. |
taken.lezen | Tasks with status, deadline and priority. |
uren.lezen | Logged hours. Amounts only if your role may see money. |
producten.lezen | Products and services with price and stock. |
tickets.lezen | Support tickets with status and priority. |
content.lezen | Posts in the content calendar with channel, date and status. |
series.lezen | Formats, series and episodes with their stage and verdict. |
radar.lezen | Ideas with score and signals from the radar: outliers, channel growth, search queries. |
social.lezen | Followers and standard figures per own social account per day. |
analytics.lezen | Visitors and search traffic from Google Analytics and Search Console. |
radar.sturen | Turn discovery on or off, steer search terms and channels and start the trend analyst. Only for whoever manages the radar in the workspace. |
radar.feedback | Mark ideas from the radar as good or not good. That only steers where the radar searches and what the trend analyst makes; nothing goes out. |
content.voorstellen | Propose a post as an idea in the content calendar. Together with Make proposals; it waits in Approvals. |
verslagen.insturen | Put reports from TrompTech Notulen into Meetings. What happens with them (tasks, contact moment, email) you choose yourself afterwards. |
verslagen.verwerken | From a report you sent in yourself, create tasks, a contact moment and a draft email, like the Process button in Meetings. Only what your role allows; an email is never sent. |
opnames.insturen | Put inspections, handovers and damage reports from TrompTech Opname on the PC into Inspections, with the photos and the report. |
opnames.lezen | Your inspections with their points and photos, to finish on the PC an inspection that started on the phone. |
uren.schrijven | Put what TrompTech Uren saw on your PC and you checked into your own timesheet as a draft, and read your own hours so nothing is logged twice. You submit it yourself; window titles are not included. |
gids.lezen | Which pages and help articles you can open in this workspace, for TrompTech Gids on your PC: it answers questions about the Layer and takes you to the right place. No data about customers or colleagues. |
content.insturen | Put carousels from TrompTech Slides and videos and shorts from TrompTech Studio into the content calendar as a post, with the images or the video. They wait for your approval; you publish them yourself. |
dictaat.insturen | Put what you dictate in TrompTech Voice into the Outbox as a task, as an internal note with a customer or as an email draft. Only what your role allows; an email is never sent. |
documenten.delen | Share proposals and reports from TrompTech Documenten with a customer as a web page, see when they open them, and store the PDF with the customer and the project. |
kennis.insturen | Put instruction videos with steps from TrompTech Uitleg into the knowledge base. |
afletteren.transacties.schrijven | Put credits from an account into reconciliation, for a personal account that is also used for business. Reading is not possible with this permission. |
huishouden.lezen | Your household budget (income, fixed costs, budgets and debts), your shopping list and your tasks. Read only, no advice. |
huishouden.schrijven | Add groceries to the list, tick them off and remove them, and add and tick off household tasks: directly, with Undo. A debt or budget always remains a proposal in Approvals. Also reads your household. |
aanbiedingen.lezen | The supermarket offers for this week and next from your offers app, and compare prices per shop for your groceries. Read only. |
voorstellen.maken | Propose changes. They wait in Approvals until someone approves them; nothing happens on its own. |
A permission only works if the module is in your subscription and your role allows it. Amounts, such as an hourly rate, are only visible if your role may see money.
3. The REST API
Everything is under /api/v1. Send the key as Authorization: Bearer. GET /api/v1 shows which resources your key can read. The full description is in /api/v1/openapi.json (OpenAPI 3.1), which you can also fetch without a key.
Reading and paging
curl "https://jouw-werkplek.example/api/v1/taken?status=open&per_pagina=20" \
-H "Authorization: Bearer $LAYER_SLEUTEL"Every list comes in pages, newest first. Use per_pagina (1 to 100, default 50) and pagina to page through; volgende gives the address of the next page, or null. Filter with status, or with sinds (a date or time in ISO 8601) to fetch only what has changed since then.
{
"data": [ { "id": "...", "titel": "...", "status": "open", "deadline": "2026-10-03T07:00:00.000Z" } ],
"pagina": 1,
"per_pagina": 20,
"totaal": 1,
"volgende": null
}| Resource | Permission | Proposals |
|---|---|---|
/api/v1/relatiesCustomers, leads and (with the Suppliers module) suppliers. | relaties.lezen | new (POST): soort, naam, bedrijf, email, telefoon, adres, postcode, plaats, kvk, btw, branche, rechtsvorm, notities, bron; change (PATCH): telefoon, branche, rechtsvorm, plaats, website, bron |
/api/v1/contactpersonenContacts at a customer, lead or supplier. | relaties.lezen | new (POST): naam, bedrijf, email, telefoon, notities, bron |
/api/v1/offertesQuotes with lines, amount and status. | offertes.lezen | change (PATCH): status |
/api/v1/facturenInvoices with lines, amount, status and payment date. | facturen.lezen | change (PATCH): status, betaald_op |
/api/v1/projectenProjects with status, deadline, tasks and progress. | projecten.lezen | change (PATCH): status, deadline |
/api/v1/takenTasks with status, deadline and priority. | taken.lezen | new (POST): titel, omschrijving, deadline, prioriteit, klant_id, project_id; change (PATCH): status, deadline, prioriteit |
/api/v1/urenLogged hours: your own, or those of the whole team if your role may see that. Rate and invoice only if your role may see that. | uren.lezen | read only |
/api/v1/productenProducts and services with price and stock. The purchase price only if your role may see money. | producten.lezen | read only |
/api/v1/ticketsSupport tickets with status, priority and category. | tickets.lezen | change (PATCH): status |
/api/v1/contentPosts in the content calendar: date, channel, topic, status and the series they belong to. | content.lezen | read only |
/api/v1/formatsFormats: the fixed structure of an episode, the form and length per platform, and the checklist. | series.lezen | read only |
/api/v1/seriesSeries: a format with cadence, account and status. | series.lezen | read only |
/api/v1/afleveringenEpisodes of all series, with date, stage, script and verdict. | series.lezen | read only |
/api/v1/radar-ideeenIdeas with a score from the content radar. The score is a band with components, never a chance of going viral. | radar.lezen | read only |
/api/v1/radar-serievoorstellenSeries suggestions from the trend analyst: a recurring show on a topic that comes back across several outliers. You create a series in the Layer itself. | radar.lezen | read only |
/api/v1/radar-signalenSignals from the last 90 days: outliers, early outliers, channel growth, search queries and news, with an explanation of how they were calculated. | radar.lezen | read only |
/api/v1/social-metingenPer own social account per day, the followers and the standard figures the channel reports (last 120 days). A missing figure is null, never zero. | social.lezen | read only |
Proposing a change
With the permission voorstellen.maken you propose a new record or a change. The response is a 202: the proposal is ready, nothing has changed yet. Explain why in waarom, so the reviewer sees it alongside.
curl -X POST "https://jouw-werkplek.example/api/v1/taken" \
-H "Authorization: Bearer $LAYER_SLEUTEL" \
-H "Content-Type: application/json" \
-d '{"titel": "Follow up on the quote", "deadline": "2026-10-03", "waarom": "Agreed in the meeting"}'HTTP/1.1 202 Accepted
{
"voorstel": { "id": "...", "actie": "taak.nieuw", "titel": "...", "status": "wacht", "link": "https://jouw-werkplek.example/app/ai/goedkeuringen?id=..." },
"uitleg": "Dit is een voorstel. Er is nog niets veranderd: het wacht in Goedkeuringen tot iemand het goedkeurt."
}Follow the status with GET /api/v1/voorstellen or GET /api/v1/voorstellen/{id}; you only see the proposals of your own key. If you prefer a webhook, subscribe to voorstel.goedgekeurd. Other actions the AI in the Layer knows, you propose with POST /api/v1/voorstellen and {"actie": "...", "gegevens": { ... }}. If an action is not possible, or something is missing, you get a 422 with the reason. Deleting is not possible via the API.
Errors
An error is always JSON: {"fout": {"code": "...", "melding": "..."}}, with a message in plain language.
| Code | Meaning |
|---|---|
| 400 | The request is not valid, for example an invalid date or a missing field. |
| 401 | No key, or a key that has expired or been revoked. |
| 403 | The key is missing a permission. The WWW-Authenticate header names which permission. |
| 404 | This address or record does not exist, or you are not allowed to see it. |
| 405 | This method is not possible here. The Allow header names what is. |
| 422 | The proposal was rejected, with the reason. |
| 429 | Too many requests. Wait the number of seconds from Retry-After. |
4. Webhooks
Whoever manages integrations in the Layer sets up webhooks under Settings, Developers, Webhooks: an address and the events you want to hear about. You see the secret for checking the signature once.
| Event | When |
|---|---|
factuur.verstuurd | An invoice is set to sent. |
factuur.betaald | An invoice is set to paid. |
offerte.geaccepteerd | A customer accepted a quote. |
ticket.nieuw | A new support ticket has come in, including from an email. |
taak.afgerond | Someone completed a task. |
voorstel.goedgekeurd | A proposal in Approvals has been approved. |
A message is deliberately thin: what happened and where you can read it, no amounts or customer data. You fetch the content with a key, via href. The id stays the same if we resend a message, so you can use it to recognise a duplicate.
POST /jouw-webhook HTTP/1.1
Content-Type: application/json
TrompTech-Event: factuur.betaald
TrompTech-Delivery: ...
TrompTech-Signature: t=1790931600,v1=...
{
"id": "evt_...",
"type": "factuur.betaald",
"aangemaakt": "2026-10-02T09:00:00.000Z",
"data": { "object": "factuur", "id": "...", "nummer": "...", "href": "/api/v1/facturen/..." }
}Checking the signature
The TrompTech-Signature header contains a time t (in seconds) and v1: an HMAC-SHA256 with your secret over t, a dot and the body as it arrived. Compute it yourself, compare in constant time and reject messages older than five minutes.
import { createHmac, timingSafeEqual } from "node:crypto";
// geheim: the full webhook secret, including the whsec_ prefix.
// inhoud: the body exactly as it arrived, as text (do not parse it as JSON first).
export function handtekeningKlopt(geheim, kop, inhoud) {
const delen = new Map((kop ?? "").split(",").map(d => d.trim().split("=", 2)));
const t = Number(delen.get("t")), v1 = delen.get("v1") ?? "";
// Older than five minutes: reject, so nobody can replay an old message.
if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > 300 || !/^[0-9a-f]{64}$/.test(v1)) return false;
const verwacht = createHmac("sha256", geheim).update(`${t}.${inhoud}`).digest();
return timingSafeEqual(verwacht, Buffer.from(v1, "hex"));
}import hashlib, hmac, re, time
def handtekening_klopt(geheim: str, kop: str, inhoud: bytes) -> bool:
delen = dict(d.strip().split("=", 1) for d in (kop or "").split(",") if "=" in d)
t, v1 = delen.get("t", ""), delen.get("v1", "")
if not t.isdigit() or abs(time.time() - int(t)) > 300 or not re.fullmatch(r"[0-9a-f]{64}", v1):
return False
verwacht = hmac.new(geheim.encode(), t.encode() + b"." + inhoud, hashlib.sha256).hexdigest()
return hmac.compare_digest(verwacht, v1)Delivery and retries
- Respond within 10 seconds with a 2xx. We do not follow a redirect (3xx).
- If it fails, we try again after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 24 hours. After that the message counts as failed; you can resend it in the Layer.
- If ten messages in a row fail, the webhook is switched off. You switch it back on yourself.
- Only https to a public address by name, on the default port. An address that points to an internal network gets nothing; we check that again on every delivery.
5. Connecting an AI app via MCP
The Layer is an MCP server at https://jouw-werkplek.example/api/mcp (Streamable HTTP). Sign-in uses OAuth 2.1 with PKCE: the app sends you to the Layer, you sign in as usual (with your second factor if you have one) and choose on the consent screen what the app may do.
- Claude: open Settings, Connectors, and add a custom connector with the address of your MCP server.
- ChatGPT: open Settings, Apps & Connectors. Under Advanced, turn on developer mode and create a connector with the address, with OAuth as the sign-in.
- Other apps: any MCP client with OAuth works. The Layer supports dynamic registration (RFC 7591) and Client ID Metadata Documents, and publishes its details at
/.well-known/oauth-authorization-serverand/.well-known/oauth-protected-resource.
The menu names in Claude and ChatGPT sometimes differ per version. The app only sees the tools that belong to the chosen permissions. Reading happens directly. Supported protocol versions: 2026-07-28, 2025-11-25, 2025-06-18 and 2025-03-26.
A plan first. The app makes a single change straight away. Several changes at once, or something that creates structure such as a project with tasks, it first records as a plan: exactly the actions with their data. You see that plan in your chat, and only after your explicit yes does the Layer carry out exactly those actions, once. Internal changes (a lead, contact moment, note, task, appointment without invitation, project or document) the Layer then carries out immediately, if your workspace allows it. You see them in Approvals and can undo them there. Anything that goes out or involves money, such as an email, an invitation, sending a quote or an invoice, always remains a proposal that you approve. A session via MCP always counts as text from outside.
A connected app keeps working until you revoke it under Settings, Devices, or until it has not been used for 30 days.
6. Questions or found a problem?
Email info@tromptech.nl. A security issue is better reported confidentially, as described under Report a security issue.