Writing Safely: Create, Update & the 'Block, Don't Delete' Pattern
Lesson 4 — The moment your agent can write, the stakes change completely. Here's the exact pattern that keeps write actions safe — including the SAP-specific gotcha that trips up almost everyone building their first CRUD agent.
Prerequisites
- Lesson 3: Your first MCP tool
The moment your agent can write, the risk profile changes instantly. A read-only agent that misbehaves gives a wrong answer. A write-capable agent that misbehaves creates a real record someone now has to notice, explain, and clean up.
Three write actions, three distinct traps
Create: the field that trips up almost everyone
const builder = businessPartnerApi.entityBuilder()
.businessPartnerCategory(category) // 1=Person, 2=Org, 3=Group — required
.businessPartnerGrouping(grouping); // required — determines the number range
BusinessPartnerGrouping determines the number range for the new record. Miss it, and you don’t get a helpful validation message — you get an error that reads like it came from somewhere else entirely.
Update: fetch first, always
const bp = await businessPartnerApi.requestBuilder().getByKey(id).execute(dest);
bp.organizationBpName1 = newName;
await businessPartnerApi.requestBuilder().update(bp).execute(dest); // SDK sends If-Match automatically
PATCH needs a CSRF token and the current ETag, every time. The SDK handles both — but only if you .getByKey() first, so it captures the ETag before you .update(). Skip that sequence, and you’ll get a 403 that has nothing obviously to do with permissions.
Delete: not a workaround, the actual pattern
API_BUSINESS_PARTNER cannot physically delete a Business Partner — ever, by design, because a BP carries audit-relevant transaction history. The correct pattern is the workaround:
const bp = await businessPartnerApi.requestBuilder().getByKey(id).execute(dest);
bp.businessPartnerIsBlocked = true;
await businessPartnerApi.requestBuilder().update(bp).execute(dest);
Design your agent’s instructions around this from day one — “you cannot delete a Business Partner; block it instead” — rather than discovering it live, when the model confidently tries to call a delete_bp tool that was never going to exist.
Try it yourself
Take your Lesson 2 write action. Write out its CSRF/ETag handling, and check whether your target API even supports a true delete — or has its own “block, don’t delete” hiding in its docs. Most SAP master-data and transactional APIs do. Assume yours does until proven otherwise.
Where this shows up in SAP
Every line above is real code from the Business Partner CRUD tutorial. Building Sales Orders instead? That tutorial shows the same discipline on header-plus-line-item creates — where the constraint differs: sales orders can be deleted, but only before a delivery or billing document exists.
Key takeaways
- Create almost always has a silently-mandatory field — find it in the docs, not in a runtime error.
- Update needs fetch-first so the SDK captures the ETag — skip it, and a missing header masquerades as an auth failure.
- “Block, don’t delete” is frequently how the API actually works, not a design choice. Bake it into the instructions from day one.
Next: your tools work locally. Time to deploy the server somewhere an agent can actually reach it.