New France e-invoicing mandate goes live September 2026 — our implementation is ready. See all mandates →
AI Agent Integration

Build an AI agent with Clearvo

Clearvo's REST API works natively with every major AI SDK. Give your agent tools for invoice submission, tax calculation, and entity management — then let it handle the compliance workflow while you focus on your product.

Prefer the MCP server? — If you're working inside Claude Code, Cursor, or Windsurf, set up the MCP server instead. This page covers building your own agent in code using the Anthropic or OpenAI SDK.

Anthropic SDK (tool_use)

Define Clearvo operations as Claude tools. Claude automatically selects the right tool, fills in the parameters from context, and hands back the result for the next reasoning step.

Install bash
npm install @anthropic-ai/sdk @clearvo/sdk
invoice-agent.ts typescript
import Anthropic from '@anthropic-ai/sdk';
import { ClearvoClient } from '@clearvo/sdk';

const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const clearvo = new ClearvoClient({ apiKey: process.env.CLEARVO_API_KEY! });

const TOOLS: Anthropic.Tool[] = [
  {
    name: 'submit_invoice',
    description: 'Submit a B2B invoice to a tax authority (Italy SDI, Poland KSeF, Romania ANAF, etc.)',
    input_schema: {
      type: 'object',
      properties: {
        country:       { type: 'string', description: 'ISO 3166-1 alpha-2 country code (e.g. IT, PL, RO)' },
        invoiceNumber: { type: 'string' },
        issueDate:     { type: 'string', description: 'YYYY-MM-DD' },
        currency:      { type: 'string', description: 'ISO 4217 currency code' },
        totalAmount:   { type: 'number', description: 'Total invoice amount including tax' },
        taxAmount:     { type: 'number' },
        supplier: {
          type: 'object',
          properties: {
            name:      { type: 'string' },
            taxId:     { type: 'string' },
            address:   { type: 'object', properties: { street: { type: 'string' }, city: { type: 'string' }, country: { type: 'string' } } },
          },
          required: ['name', 'taxId', 'address'],
        },
        buyer: {
          type: 'object',
          properties: {
            name:    { type: 'string' },
            taxId:   { type: 'string' },
            address: { type: 'object', properties: { street: { type: 'string' }, city: { type: 'string' }, country: { type: 'string' } } },
          },
          required: ['name', 'address'],
        },
        lines: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              description: { type: 'string' },
              quantity:    { type: 'number' },
              unitPrice:   { type: 'number' },
              lineAmount:  { type: 'number' },
            },
            required: ['description', 'quantity', 'unitPrice', 'lineAmount'],
          },
        },
      },
      required: ['country', 'invoiceNumber', 'issueDate', 'currency', 'totalAmount', 'taxAmount', 'supplier', 'buyer', 'lines'],
    },
  },
  {
    name: 'poll_status',
    description: 'Check the clearance status of a submitted invoice',
    input_schema: {
      type: 'object',
      properties: { referenceId: { type: 'string', description: 'The referenceId returned by submit_invoice' } },
      required: ['referenceId'],
    },
  },
  {
    name: 'get_requirements',
    description: 'Get e-invoicing requirements for a country before submitting',
    input_schema: {
      type: 'object',
      properties: { country: { type: 'string', description: 'ISO 3166-1 alpha-2 country code' } },
      required: ['country'],
    },
  },
];

async function runTool(name: string, input: Record<string, unknown>) {
  if (name === 'submit_invoice') {
    return clearvo.submitInvoice(input as Parameters<typeof clearvo.submitInvoice>[0]);
  }
  if (name === 'poll_status') {
    return clearvo.getInvoiceStatus(input.referenceId as string);
  }
  if (name === 'get_requirements') {
    return clearvo.getRequirements(input.country as string);
  }
  throw new Error(`Unknown tool: ${name}`);
}

export async function invoiceAgent(prompt: string): Promise<string> {
  const messages: Anthropic.MessageParam[] = [{ role: 'user', content: prompt }];

  while (true) {
    const response = await anthropic.messages.create({
      model: 'claude-opus-4-8',
      max_tokens: 4096,
      tools: TOOLS,
      messages,
    });

    if (response.stop_reason === 'end_turn') {
      const text = response.content.find(b => b.type === 'text');
      return text ? text.text : '';
    }

    // Process tool calls
    const toolUses = response.content.filter(b => b.type === 'tool_use');
    if (!toolUses.length) break;

    messages.push({ role: 'assistant', content: response.content });

    const toolResults: Anthropic.ToolResultBlockParam[] = [];
    for (const block of toolUses) {
      if (block.type !== 'tool_use') continue;
      try {
        const result = await runTool(block.name, block.input as Record<string, unknown>);
        toolResults.push({ type: 'tool_result', tool_use_id: block.id, content: JSON.stringify(result) });
      } catch (err) {
        toolResults.push({ type: 'tool_result', tool_use_id: block.id, content: `Error: ${err}`, is_error: true });
      }
    }
    messages.push({ role: 'user', content: toolResults });
  }

  return 'Agent loop ended unexpectedly';
}

// Usage
const result = await invoiceAgent(
  'Check the e-invoicing requirements for Italy, then submit an invoice for €2,000 + 22% VAT ' +
  '(€440) from Clearvo Ltd (IE1234567T, Dublin) to Acme SpA (IT12345678901, Milan) for "Software licence Q3 2026"'
);
console.log(result);

OpenAI SDK (function calling)

The same pattern works with gpt-4o and any OpenAI-compatible model via function calling.

Install bash
npm install openai @clearvo/sdk
invoice-agent-openai.ts typescript
import OpenAI from 'openai';
import { ClearvoClient } from '@clearvo/sdk';

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const clearvo = new ClearvoClient({ apiKey: process.env.CLEARVO_API_KEY! });

const FUNCTIONS: OpenAI.FunctionDefinition[] = [
  {
    name: 'calculate_tax',
    description: 'Calculate VAT, GST, or sales tax for a transaction in 100+ countries',
    parameters: {
      type: 'object',
      properties: {
        currency: { type: 'string' },
        seller: {
          type: 'object',
          properties: {
            address: { type: 'object', properties: { country: { type: 'string' } } },
            taxId:   { type: 'string', description: 'Seller VAT/tax number' },
          },
          required: ['address'],
        },
        customer: {
          type: 'object',
          properties: {
            type:           { type: 'string', enum: ['B2B', 'B2C'], description: 'B2B for business customers, B2C for consumers' },
            taxId:          { type: 'string', description: 'Customer VAT number (B2B only)' },
            billingAddress: { type: 'object', properties: { country: { type: 'string' } } },
          },
          required: ['type'],
        },
        lineItems: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              id:          { type: 'string' },
              amount:      { type: 'number', description: 'Line amount in minor currency units (cents)' },
              productName: { type: 'string' },
            },
            required: ['id', 'amount', 'productName'],
          },
        },
        commit: { type: 'boolean', description: 'Set true to record in audit trail; false for dry run' },
      },
      required: ['currency', 'seller', 'customer', 'lineItems'],
    },
  },
  {
    name: 'validate_tax_number',
    description: 'Validate a business VAT or tax number against the official authority',
    parameters: {
      type: 'object',
      properties: {
        country:   { type: 'string', description: 'ISO 3166-1 alpha-2 country code' },
        taxNumber: { type: 'string', description: 'The VAT or tax number to validate (include country prefix for EU VAT)' },
      },
      required: ['country', 'taxNumber'],
    },
  },
];

export async function taxAgent(prompt: string): Promise<string> {
  const messages: OpenAI.ChatCompletionMessageParam[] = [
    { role: 'system', content: 'You are a tax compliance assistant. Use the available tools to answer questions accurately.' },
    { role: 'user', content: prompt },
  ];

  while (true) {
    const response = await openai.chat.completions.create({
      model: 'gpt-4o',
      tools: FUNCTIONS.map(f => ({ type: 'function' as const, function: f })),
      messages,
    });

    const choice = response.choices[0];
    if (choice.finish_reason === 'stop') {
      return choice.message.content ?? '';
    }

    if (choice.finish_reason !== 'tool_calls' || !choice.message.tool_calls?.length) break;

    messages.push(choice.message);

    for (const call of choice.message.tool_calls) {
      const args = JSON.parse(call.function.arguments) as Record<string, unknown>;
      let result: unknown;
      try {
        if (call.function.name === 'calculate_tax') {
          result = await clearvo.calculateTax(args as Parameters<typeof clearvo.calculateTax>[0]);
        } else if (call.function.name === 'validate_tax_number') {
          result = await clearvo.validateTaxNumber(args.country as string, args.taxNumber as string);
        }
      } catch (err) {
        result = { error: String(err) };
      }
      messages.push({ role: 'tool', tool_call_id: call.id, content: JSON.stringify(result) });
    }
  }

  return 'Agent loop ended unexpectedly';
}

// Usage
const result = await taxAgent(
  'Is DE123456789 a valid German VAT number? If so, calculate the tax for a €10,000 B2B sale ' +
  'from Ireland (IE1234567T) to this German business.'
);
console.log(result);

Agent patterns

Check requirements first

Always call get_requirements before submit_invoice for a new country. Requirements include the VAT number format, whether e-invoicing is mandatory, the Peppol scheme, and the authority the invoice routes to. Build this into your agent's system prompt.

Poll until settled

Invoice clearance is asynchronous. Italy SDI typically settles in minutes; KSeF is near-instant. Poll poll_status every 10–30 seconds until status is ACCEPTED, REJECTED, or FAILED. Build a timeout (e.g. 5 minutes) and surface the referenceId so the user can check later.

Validate before submitting

Run validate_tax_number on the buyer's VAT number before submission. Authorities reject invoices with invalid buyer tax IDs — catching this before submission avoids the resubmission cycle and gives users an actionable error.

Use sandbox keys for dev

csk_test_... keys route to simulated authority responses — no real submissions, no real clearance required. Italy and Romania respond PENDING then auto-resolve to ACCEPTED after 30 seconds. Use these in CI/CD pipelines and agent testing.

Full invoice workflow agent

A complete agent that handles the end-to-end flow: fetch requirements → validate buyer → submit → poll → confirm.

System prompt (add to your agent)
You are a B2B invoice compliance agent. When asked to submit an invoice:

1. Call get_requirements for the destination country to understand format requirements.
2. If the buyer has a VAT number, call validate_tax_number to confirm it is valid.
3. Call submit_invoice with all required fields populated correctly.
4. Poll poll_status every 15 seconds until status is ACCEPTED, REJECTED, or FAILED.
5. Report the final status and referenceId to the user.

Key rules:
- issueDate must be YYYY-MM-DD format
- totalAmount and taxAmount must be in the invoice currency (not minor units)
- For Italy: buyer.taxId is the Italian P.IVA (IT followed by 11 digits)
- For Poland: supplier must have vatNumber registered in PL
- Never submit with totalAmount = 0 — validate amounts before calling the tool
- If requirements show eInvoicingMandatory: false, warn the user but proceed if asked

Supplier onboarding agent

An agent that validates a list of supplier VAT numbers and flags issues before invoices are submitted.

supplier-validator.ts typescript
import Anthropic from '@anthropic-ai/sdk';
import { ClearvoClient } from '@clearvo/sdk';

const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const clearvo = new ClearvoClient({ apiKey: process.env.CLEARVO_API_KEY! });

interface Supplier { name: string; country: string; taxNumber: string; }

export async function validateSuppliers(suppliers: Supplier[]): Promise<void> {
  // Run validations in parallel — no need for an agentic loop here
  const results = await Promise.all(
    suppliers.map(async (s) => {
      const validation = await clearvo.validateTaxNumber(s.country, s.taxNumber);
      return { supplier: s, validation };
    })
  );

  const invalid = results.filter(r => !r.validation.valid);
  if (!invalid.length) {
    console.log(`All ${suppliers.length} suppliers validated successfully.`);
    return;
  }

  // Use Claude to generate human-readable remediation guidance
  const response = await anthropic.messages.create({
    model: 'claude-haiku-4-5-20251001',
    max_tokens: 1024,
    messages: [{
      role: 'user',
      content: `These supplier tax numbers failed validation. For each, explain the likely issue and suggest how to fix it.
${JSON.stringify(invalid, null, 2)}`,
    }],
  });

  const text = response.content.find(b => b.type === 'text');
  if (text) console.log(text.text);
}

// Usage
await validateSuppliers([
  { name: 'Acme SpA',    country: 'IT', taxNumber: 'IT12345678901' },
  { name: 'Widget GmbH', country: 'DE', taxNumber: 'DE999999999' },  // invalid
  { name: 'Tech Ltd',    country: 'GB', taxNumber: 'GB123456789' },
]);

Ready to build your agent?

Get a production API key in seconds. No sales call.

Get your free API key Set up MCP instead