creating-offers

Installation
SKILL.md

Creating Offers on angebote.lenne.tech

This skill enables Claude Code to create, optimize, and manage business offers on the Offers platform via MCP tools, for whichever company the signed-in account belongs to.

Gotchas

  • Content block order values must be ascending without gaps — Gaps in the sequence (e.g., 1, 3, 5) cause rendering glitches on the offers frontend. When deleting a block, re-normalize remaining orders; when inserting, pick the next consecutive integer. The API does not validate this — the bug only surfaces client-side.
  • global-ref blocks point at blocks the platform provides — list_globals shows which reusable blocks exist and get_global shows their versions. The MCP catalog has no tool to create one, so reuse your own recurring content through the knowledge base or a template offer instead (see "Reusing content across offers").
  • OAuth sign-ins are stored per endpoint; a failed refresh needs /mcp — Claude Code stores the offers-api and offers-api-demo sign-ins per endpoint, shared across sessions, so each server is authorized on its own: the first call against offers-api-demo starts its own OAuth flow even when offers-api is already signed in. Access tokens expire after an hour; on a 401 Claude Code refreshes the token and retries once. When the refresh fails, open /mcp and authenticate the server again. /mcp also reconnects a server that timed out while connecting. A deploy used to look identical from the client side and is a different problem, now fixed (DEV-3408): the server holds MCP sessions in process memory, so restarting the container invalidates every session id in the wild. It answered that with 400 No valid session, on which a client does not recover — so every connected session stayed dead until somebody ran /mcp by hand. It now answers 404, and the client sends a fresh initialize by itself. So a 400 after a deploy is no longer expected; a 401 is still the authorization case above.
  • Template offers cannot be published — only duplicated — Offers with isTemplate: true cannot be mark_sent. Attempting to publish a template silently returns the unchanged offer. To publish, first create_from_template to produce a regular offer, then send that one.
  • Hardcoded colors in custom-html break in the other color mode — A block styled with inline colors for a light page turns unreadable when the viewer flips the theme toggle: dark headings and dark body text end up on the dark page background. colorMode: 'light' does not prevent this — it only sets the initial preference, the toggle stays available. Every custom-html block must paint its own background on the outermost element whenever it sets text colors. See custom-html-guide.md → "Readability in both color modes".
  • cta.text is rendered as plain text, not HTML — Passing "<p>…</p>" prints the literal tags on the offer page. The block docs list it next to HTML-bearing fields, which invites the mistake. Pass a bare sentence. text blocks, custom-html and faq answers are unaffected.
  • Embedded credentials in links (https://user:pass@host) are blocked by Chrome — The navigation fails with ERR_FAILED, so a "one-click" demo link built that way is dead on arrival for most recipients. Link the plain URL and list the basic-auth credentials next to it so the browser prompt can be answered.
  • Customer quotes must be verbatim and complete — Shortening a testimonial or reference.quote to its "relevant" part, or silently fixing a typo in it, misrepresents a real person. Copying a quote out of an older offer is not safe either: it may already be truncated there. Pull the canonical wording from the company's own published source (the knowledge base, or the references page it links to) and diff it character by character — see best-practices.md → "Customer quotes are verbatim, always".
  • File fields survive an update that omits them, so a new block inherits the old block at the same position — update_offer replaces the block array, but OfferService.update() first runs mergeContentBlockFiles(), which pairs stored and incoming blocks by ${order}-${type} and then protects every file field: imageFileId, fileId, animationFileId, previewFileId, fileIds, members[].imageFileId, files[].fileId. Omitting the field or sending "" means keep what is stored — only an explicit null clears it. Because the pairing is positional, a brand-new reference inserted where another reference used to sit silently adopts that block's imageFileId, and the offer then shows the wrong screenshot under the new project name. Whenever a block changes identity, send a real file id or null, never "". See content-blocks.md → "File fields on update".
  • A reference block without an image renders an empty placeholder box — The renderer always reserves the image column and falls back to a grey box with an image icon, which reads as broken on a customer-facing page. Every reference needs a real imageFileId. When no product screenshot exists, a purpose-built diagram is a legitimate substitute; a screenshot of a different project is not.

When to Use This Skill

Installs
1
First Seen
Oct 27, 2026