Skip to content

Setup guide · Framework

Formfeed in Plain tool calling

MCP is the right choice when the agent should discover templates. When it should only ever produce invoices, a tool of your own is simpler: its schema is the invoice, the model cannot pick the wrong template, and the SDK does retries and idempotency.

  1. Create a test key

    Sign in to the app, open API keys and create a key in the test environment. Test renders are not metered, which is what you want while an agent is still learning your templates. Switch to a live key when it goes to production.

  2. Install the packages

    Shell
    npm install @anthropic-ai/sdk @formfeed/sdk zod
    # or: pip install anthropic formfeed
  3. Add the Formfeed server

    The tool calls renders.create with a fixed template. The same function works as a tool in any framework: OpenAI function calling, LangChain @tool, or the Claude tool runner shown here.

    invoice-agent.tsTypeScript
    import Anthropic from '@anthropic-ai/sdk';
    import { betaZodTool } from '@anthropic-ai/sdk/helpers/beta/zod';
    import { Formfeed } from '@formfeed/sdk';
    import { z } from 'zod';
    
    const anthropic = new Anthropic();
    const formfeed = new Formfeed({ apiKey: process.env.FORMFEED_API_KEY! });
    
    const renderInvoice = betaZodTool({
      name: 'render_invoice',
      description: 'Renders an invoice PDF with the invoice-de template and returns its download URL.',
      inputSchema: z.object({
        number: z.string(),
        customer: z.object({ name: z.string(), street: z.string(), zip: z.string(), city: z.string() }),
        lines: z.array(z.object({ description: z.string(), qty: z.number(), price: z.number() })),
      }),
      run: async (invoice) => {
        const render = await formfeed.renders.create({ template: 'invoice-de', data: { invoice } });
        const done = render.status === 'succeeded' ? render : await formfeed.renders.waitFor(render.id);
        return done.download_url ?? `Render ${done.id} failed: ${done.error?.message}`;
      },
    });
    
    const message = await anthropic.beta.messages.toolRunner({
      model: 'claude-opus-5-5',
      max_tokens: 16000,
      tools: [renderInvoice],
      messages: [
        {
          role: 'user',
          content: 'Invoice Olvarest GmbH, Beispielweg 2, 54321 Beispielstadt: 8 h consulting at 120 EUR. Number 2026-0042.',
        },
      ],
    });
    console.log(message.content);
    certificate_agent.pyPython
    import os
    
    import anthropic
    from anthropic import beta_tool
    from formfeed import Formfeed
    
    formfeed = Formfeed(api_key=os.environ["FORMFEED_API_KEY"])
    
    
    @beta_tool
    def render_certificate(name: str, course: str, date: str) -> str:
        """Renders a course certificate as PDF and returns the download URL.
    
        Args:
            name: Full name of the participant.
            course: Title of the course.
            date: Completion date as ISO 8601, e.g. 2026-09-10.
        """
        data = {"name": name, "course": course, "date": date}
        render = formfeed.renders.create(template="certificate", data=data)
        return render.download_url or f"Render {render.id} is {render.status}"
    
    
    prompt = "Jane Doe finished Analytical Engines 101 today. I need her certificate."
    runner = anthropic.Anthropic().beta.messages.tool_runner(
        model="claude-opus-5-5",
        max_tokens=16000,
        tools=[render_certificate],
        messages=[{"role": "user", "content": prompt}],
    )
    message = runner.until_done()
    print(next(block.text for block in message.content if block.type == "text"))
  4. Check the connection

    Run it with a test key: the render is not metered and shows up in the app’s render log, with the data the model sent if the workspace keeps request data (a setting of the paid plans, off by default).

Try it

  • Invoice

    “Render invoice 2026-0042 for Olvarest GmbH: 8 hours of consulting at €120 and 20 hours of implementation at €110. Use the German invoice template.”

  • Validation

    “Check whether orders/2026-0042.json fits the invoice-de template before you render anything.”

  • Ad-hoc HTML

    “Turn these meeting notes into a one-page PDF. No template, plain HTML is fine.”

Tips

  • Copy the field names from the template’s schema (GET /v1/templates/{id}/schema) into the tool schema; the editor infers it from your sample data.
  • The SDK sends an idempotency key with every render, so a retried tool call does not create a second invoice.

The eight tools

list_templates
Templates of the workspace with slug, kind (PDF, image, Word or PowerPoint), engine and published version; filter by kind, engine, tag or search text.
free
get_template_schema
The JSON Schema of the data a template expects, with the sample data it was designed with. A template without a stored schema gets one inferred from its sample data: a guide, not a contract.
free
validate_template
Checks a template with the data without rendering: syntax, variables missing from the data, errors that only happen with this data, and the data against the template’s stored schema. Ad-hoc HTML is checked offline.
free
check_invoice
Checks the _invoice block of an e-invoice offline, as the API does before it renders one: the fields EN 16931 asks for, the totals to the cent, VAT categories and code lists, and the rules of France. Names every field and rule it breaks.
free
render
Renders a template or raw HTML to PDF, PNG, JPG or WebP (Word and PowerPoint templates also to DOCX or PPTX) and returns the download URL, page count and units. With einvoice, the PDF becomes a ZUGFeRD / Factur-X e-invoice with the validator’s report, for one unit more (live from the Starter plan).
1 unit, PDFs per 5 pages
convert_to_pdf
Converts a Word, Excel, PowerPoint, OpenDocument, RTF or HTML document of up to 20 MB, or the render of a Word or PowerPoint template, to PDF and returns the download URL; the document is not kept. The local server also reads a file by its path. Starter plan and above.
like a PDF
get_render
Status, download URL and error of a render: for renders the agent queued with wait: false, and for a fresh link once the old one has expired.
free
get_workspace
The workspace and organisation the server works in, the plan, the environment and the units of the period: how many renders are left.
free

Configuration reference: Formfeed SDK — Plain tool calling (checked 17 October 2026)

Other setup guides

Automation

Start rendering today

The free plan includes 100 units a month and free test renders. Change plans when you need more.