惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 叶小钗
C
Check Point Blog
宝玉的分享
宝玉的分享
Attack and Defense Labs
Attack and Defense Labs
www.infosecurity-magazine.com
www.infosecurity-magazine.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Application and Cybersecurity Blog
Application and Cybersecurity Blog
博客园 - Franky
V
V2EX
Hugging Face - Blog
Hugging Face - Blog
Google DeepMind News
Google DeepMind News
罗磊的独立博客
S
SegmentFault 最新的问题
S
Secure Thoughts
T
Troy Hunt's Blog
J
Java Code Geeks
Last Week in AI
Last Week in AI
酷 壳 – CoolShell
酷 壳 – CoolShell
W
WeLiveSecurity
Help Net Security
Help Net Security
S
Security @ Cisco Blogs
T
Threatpost
Apple Machine Learning Research
Apple Machine Learning Research
D
Darknet – Hacking Tools, Hacker News & Cyber Security
V2EX - 技术
V2EX - 技术
T
Tor Project blog
S
Security Affairs
T
Tailwind CSS Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
H
Hacker News: Front Page
腾讯CDC
博客园 - 司徒正美
The Last Watchdog
The Last Watchdog
N
News | PayPal Newsroom
博客园 - 聂微东
小众软件
小众软件
WordPress大学
WordPress大学
博客园 - 三生石上(FineUI控件)
爱范儿
爱范儿
C
CERT Recently Published Vulnerability Notes
AI
AI
N
News and Events Feed by Topic
C
Cybersecurity and Infrastructure Security Agency CISA
O
OpenAI News
T
The Exploit Database - CXSecurity.com
L
LINUX DO - 最新话题
T
Threat Research - Cisco Blogs
雷峰网
雷峰网
NISL@THU
NISL@THU
V
Visual Studio Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
How to Integrate with the Rillet API in 2026
Kate Apideck · 2026-06-19 · via DEV Community

Rillet has quietly become one of the more interesting accounting platforms for SaaS and fintech companies. Built on REST principles, following the OpenAPI Specification, and recently shipping an MCP server — it's a system that's clearly designed by people who have actually integrated with legacy ERPs and didn't want to recreate that pain.

This guide covers everything you need to get up and running with the Rillet API: authentication, pagination, idempotency, webhooks, error handling, and the newer MCP server setup. Whether you're syncing financial data from a payment processor, building a custom integration between Rillet and your internal systems, or using an AI coding agent to accelerate development, this should give you a solid foundation.

If you'd rather skip the direct integration work, Apideck now supports Rillet as a connector through the Unified Accounting API. That means you can read and write Rillet data through the same API you'd use for QuickBooks, Xero, NetSuite, or any of 30+ other accounting platforms. More on that at the end of this guide.


What the Rillet API Actually Is

Before jumping into code, it's worth understanding the scope. The Rillet API is a REST API that gives you programmatic access to everything a user can do in the UI: customers, invoices, payments, credit memos, journal entries, chart of accounts, reports (balance sheet, trial balance), and more. It's versioned, paginated, and follows RFC 9457 for structured error responses — which is a good sign.

The API spec is available as an OpenAPI download at https://docs.api.rillet.com/openapi, which means you can generate client code in any language you prefer without hand-rolling everything.


Environments

Rillet provides two environments:

  • Production: https://api.rillet.com
  • Sandbox: https://sandbox.api.rillet.com

Start in sandbox. The sandbox mirrors production behavior, so anything you build there will work in production without surprises. All examples in this guide use the sandbox URL.


Authentication

Rillet uses API key authentication. To get a key, contact your Rillet team to enable API access, then create and manage keys in your Organization Settings under API Access.

Every request needs the key in the Authorization header as a Bearer token:

curl --request GET \
  --url https://sandbox.api.rillet.com/customers \
  --header 'Authorization: Bearer YOUR_API_KEY'

That's it. No OAuth dance, no token expiry to manage. Keep your key in an environment variable and never hardcode it.

const RILLET_API_KEY = process.env.RILLET_API_KEY;

const headers = {
  'Authorization': `Bearer ${RILLET_API_KEY}`,
  'Content-Type': 'application/json',
};


API Versioning

The API is versioned, and you should always target a specific version explicitly rather than relying on the default (which falls back to v1.0). Pass the version in an HTTP header:

curl --request GET \
  --url https://sandbox.api.rillet.com/customers \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'X-Rillet-API-Version: 3'

At the time of writing, v3 is the current version. Pinning your version header means you won't get unexpectedly broken by a major API update. Check the docs for the exact format — the changelog indicates versions are referenced as integers (2, 3), not decimals.


Pagination

All list endpoints use keyset-based pagination, which is the right choice for financial data where consistent page sizes and response times matter more than offset-based approaches.

Responses come back in reverse chronological order and include a pagination object:

{
  "data": [...],
  "pagination": {
    "next_cursor": "VdW1ptsZbOB4E1fq"
  }
}

To fetch the next page, pass the cursor as a query parameter:

curl "https://sandbox.api.rillet.com/invoices?cursor=VdW1ptsZbOB4E1fq"

A few things to know:

  • Cursors are valid for 2 hours. If yours expires, a new pagination sequence starts from the first page.
  • The default page size is 25. You can set it with the limit parameter, up to a maximum of 100.
  • When there's no next_cursor in the response, you've reached the last page.

Here's a simple function to paginate through all invoices:

async function getAllInvoices() {
  const invoices = [];
  let cursor = null;

  do {
    const url = new URL('https://sandbox.api.rillet.com/invoices');
    url.searchParams.set('limit', '100');
    if (cursor) url.searchParams.set('cursor', cursor);

    const response = await fetch(url, { headers });
    const data = await response.json();

    invoices.push(...data.data);
    cursor = data.pagination?.next_cursor ?? null;
  } while (cursor);

  return invoices;
}


Incremental Sync with updated_at Filters

Rillet has been rolling out updated_at timestamps and updated.gt filter support across its entities. As of February 2026, accounts and custom fields explicitly gained both the timestamp field and the filter parameter. Other entities — customers, invoices, payments, credit memos, and journal entries — already carried updated_at in responses, though filter support varies by entity; check the changelog and OpenAPI spec for what's available on each.

For accounts specifically, confirmed as of February 2026:

curl "https://sandbox.api.rillet.com/accounts?updated.gt=2026-02-01T00:00:00Z" \
  --header 'Authorization: Bearer YOUR_API_KEY'

For reporting journal entries, the endpoint also supports updated.gt filtering:

curl "https://sandbox.api.rillet.com/reports/journal-entries?updated.gt=2026-02-01T00:00:00Z" \
  --header 'Authorization: Bearer YOUR_API_KEY'

The general pattern — poll periodically, store the last sync timestamp, fetch only what's changed — is the right approach for building a sync pipeline. Before assuming updated.gt works on a given entity, verify it against the current OpenAPI spec at https://docs.api.rillet.com/openapi.


Idempotency

For POST requests (creating invoices, customers, journal entries, etc.), Rillet supports idempotency keys. This is critical if you're dealing with network failures or retries — you don't want to double-create an invoice because a request timed out.

Pass a unique key in the Idempotency-Key header:

curl --request POST \
  --url https://sandbox.api.rillet.com/invoices \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Idempotency-Key: f0e9a51e-905d-4caf-a5dc-64d326574646' \
  --header 'Content-Type: application/json' \
  --data '{"customer_id": "cust_123", ...}'

A few rules:

  • Use UUID v4 for your keys — high entropy, low collision risk.
  • The same response is returned for 24 hours after the first successful request. After that, the same key will create a new object.
  • If a request fails due to a validation error, the response isn't saved and you can safely retry with the same key.
  • If a second request arrives while the first is still processing, you'll get a 409 Conflict.

Rate Limiting

The limit is 60 requests per rolling minute. Requests over that threshold return HTTP 429. Build in retry logic with exponential backoff:

async function rilletRequest(url, options, retries = 3) {
  for (let attempt = 0; attempt < retries; attempt++) {
    const response = await fetch(url, { ...options, headers });

    if (response.status === 429) {
      const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
      await new Promise(resolve => setTimeout(resolve, delay));
      continue;
    }

    return response;
  }

  throw new Error('Rate limit exceeded after retries');
}

At 60 requests per minute, you're unlikely to hit this in normal operations unless you're running a bulk sync. If you are, batch your calls and space them out accordingly.


Error Handling

Rillet follows RFC 9457 for structured error responses. A 4xx error looks like this:

{
  "type": "https://rillet.com/illegal-argument",
  "title": "Bad Request",
  "status": 400,
  "detail": "The start date (2026-01-01) must not be after the end date (2025-12-31). Please review the contract item."
}

The detail field is human-readable and actually useful — it tells you what went wrong. Parse the type field for programmatic error handling, and surface detail in your logs.

async function handleRilletResponse(response) {
  if (!response.ok) {
    const error = await response.json().catch(() => null);
    const detail = error?.detail ?? `HTTP ${response.status}`;
    throw new Error(`Rillet API error: ${detail}`);
  }
  return response.json();
}


Monetary Values

Financial amounts are returned as strings, not floats — which is the correct approach for avoiding floating point precision issues. Each amount comes with a currency code:

{
  "amount": "100.99",
  "currency": "USD"
}

The currency field is always an ISO-4217 three-letter code. Note that Rillet allows more decimal places than the ISO standard for certain fields like unit prices, so don't assume two decimal places — parse flexibly.

Use a decimal library (like decimal.js or Python's decimal module) rather than JavaScript's native Number when doing arithmetic on these values.


Webhooks

Rather than polling for changes, you can subscribe to real-time events via webhooks. Rillet supports events across entities like Invoice, Customer, Payment, and Credit Memo, with event types like CREATED and UPDATED.

Setup

Go to Organization Settings > Webhooks in the Rillet dashboard:

  1. Give it a name.
  2. Provide a publicly accessible HTTPS URL.
  3. Select the events you want to receive.
  4. Enable the webhook.

An organization can have up to 5 webhooks. The recommendation from Rillet's docs is to use a single endpoint and handle routing in your application.

Incoming Request Structure

Every webhook request is a POST with a JSON body and these headers:

Header Description
X-Rillet-Signature HMAC-SHA256 signature (Base64-encoded)
X-Rillet-Timestamp ISO 8601 timestamp
X-Rillet-Id Unique UUID for this delivery
X-Rillet-Entity Entity type (e.g., INVOICE)
X-Rillet-Event Event type (e.g., CREATED)

Your endpoint must respond with a 2xx status within 30 seconds. Rillet retries failed deliveries in groups of 3 with exponential backoff, up to 5 groups before marking the webhook as failed.

Use X-Rillet-Id for idempotency on your side — deduplicate incoming events by this ID.

Verifying Signatures

Always verify the signature. The signature is an HMAC-SHA256 hash of a concatenated payload, Base64-encoded. The signed payload format is:

{timestamp}.{id}.{entity}.{event}.{raw_body}

Here's a TypeScript implementation:

import * as crypto from 'crypto';
import { Buffer } from 'node:buffer';

export function verifyRilletWebhook(
  headers: Record<string, string>,
  rawBody: string,
  token: string
): void {
  const signatures = (headers['X-Rillet-Signature'] ?? '')
    .split(',')
    .map(s => s.trim())
    .filter(Boolean);

  const timestamp = headers['X-Rillet-Timestamp'];
  const id = headers['X-Rillet-Id'];
  const entity = headers['X-Rillet-Entity'];
  const event = headers['X-Rillet-Event'];

  if (!signatures.length || signatures.length > 10 || !timestamp || !id || !entity || !event) {
    throw new Error('Missing or invalid Rillet webhook headers');
  }

  const signedPayload = `${timestamp}.${id}.${entity}.${event}.${rawBody}`;
  const tokenBytes = Buffer.from(token, 'base64');

  const verified = signatures.some(sig => {
    const expected = crypto
      .createHmac('sha256', tokenBytes)
      .update(signedPayload)
      .digest('base64');

    const receivedBuf = Buffer.from(sig, 'base64');
    const expectedBuf = Buffer.from(expected, 'base64');

    return (
      receivedBuf.length === expectedBuf.length &&
      crypto.timingSafeEqual(receivedBuf, expectedBuf)
    );
  });

  if (!verified) throw new Error('Invalid webhook signature');
}

And the Python equivalent:

import hashlib
import hmac
import base64

def verify_rillet_webhook(headers, raw_body, token):
    signatures = [s.strip() for s in headers.get('X-Rillet-Signature', '').split(',') if s.strip()]
    timestamp = headers.get('X-Rillet-Timestamp')
    event_id = headers.get('X-Rillet-Id')
    entity = headers.get('X-Rillet-Entity')
    event = headers.get('X-Rillet-Event')

    if not signatures or len(signatures) > 10 or not all([timestamp, event_id, entity, event]):
        raise ValueError('Missing or invalid Rillet webhook headers')

    signed_payload = f"{timestamp}.{event_id}.{entity}.{event}.{raw_body}"
    token_bytes = base64.b64decode(token)

    for sig in signatures:
        expected = hmac.new(token_bytes, signed_payload.encode('utf-8'), hashlib.sha256).digest()
        received = base64.b64decode(sig)
        if hmac.compare_digest(received, expected):
            print('Signature verified.')
            return

    raise ValueError('Invalid webhook signature')

One edge case worth knowing: the signature header can contain up to 10 comma-separated signatures to support token rotation without downtime. Your code should verify any of them, not just the first.


Using the Rillet MCP Server

This is the part that's new in 2026 and genuinely useful if you're using AI coding agents to build or maintain your integration.

Rillet ships a remote MCP server at https://docs.api.rillet.com/mcp. You can connect AI tools like Claude Code, Cursor, Windsurf, or Claude Desktop to it, and they'll have direct access to your Rillet account data and documentation. This means your AI assistant can look up your actual customers, check for overdue invoices, or generate integration code that reflects your real chart of accounts.

Setting Up with Claude Code

# Sandbox
claude mcp add --transport http rillet-mcp-sandbox \
  https://docs.api.rillet.com/mcp \
  --header "Authorization: Bearer YOUR_SANDBOX_KEY"

# Production
claude mcp add --transport http rillet-mcp-prod \
  https://docs.api.rillet.com/mcp \
  --header "Authorization: Bearer YOUR_PROD_KEY"

# Verify
claude mcp list

When prompting your AI agent, specify which environment to use: "Use the Rillet MCP in sandbox." From there you can ask questions like "What customers do I have?" or "Do I have any overdue invoices?" and get answers grounded in your actual data.

This is particularly useful during development — instead of manually reading through API docs and constructing test requests by hand, you can have an AI agent explore the API surface against your sandbox data while you're building.


Pulling Financial Reports

Rillet has been adding financial report endpoints through early 2026. The current set includes:

# Balance sheet
GET /reports/balance-sheet

# Trial balance
GET /reports/trial-balance

# Income statement
GET /reports/income-statement

# Cash flow statement
GET /reports/cash-flow-statement

All four support subsidiary_id and breakdown_by parameters for multi-entity setups. The trial balance returns beginning balance, debits, credits, and ending balance per account. The income statement and cash flow statement both accept a date range. There's also a reporting journal entries endpoint (GET /reports/journal-entries) that returns amounts converted to the subsidiary's reporting currency — useful for multi-currency consolidations.


The Unified API Alternative: Apideck's Rillet Connector

If you're building a product that needs to support multiple accounting platforms and Rillet is one of them, there's a faster path than building a direct integration from scratch.

Apideck now supports Rillet as a connector through the Unified Accounting API. You can use a single API to read and write data across Rillet, QuickBooks, Xero, NetSuite, Sage, and 30+ other accounting systems.

The trade-off is the same as any abstraction layer: you get breadth and speed at the cost of some depth. The Unified API covers the core accounting objects (invoices, payments, customers, journal entries, chart of accounts) that most integrations need. If your product requires Rillet-specific features like the financial report endpoints or the MCP server, you'll still want the direct integration described above.

For many use cases, starting with Apideck's connector and adding direct Rillet API calls where you need deeper functionality is the pragmatic move. Build once, connect to every accounting platform your customers use, and go deeper on Rillet where it matters.


A Minimal Integration Example

To tie this all together, here's a minimal Node.js integration that fetches invoices from Rillet:

import fetch from 'node-fetch';

const BASE_URL = 'https://api.rillet.com';
const API_KEY = process.env.RILLET_API_KEY;
const headers = {
  'Authorization': `Bearer ${API_KEY}`,
  'X-Rillet-API-Version': '3',
};

async function syncInvoices() {
  const invoices = [];
  let cursor = null;

  do {
    const url = new URL(`${BASE_URL}/invoices`);
    url.searchParams.set('limit', '100');
    if (cursor) url.searchParams.set('cursor', cursor);

    const response = await fetch(url.toString(), { headers });

    if (!response.ok) {
      const error = await response.json().catch(() => ({}));
      throw new Error(`Rillet error: ${error.detail ?? response.status}`);
    }

    const data = await response.json();
    invoices.push(...data.data);
    cursor = data.pagination?.next_cursor ?? null;
  } while (cursor);

  return invoices;
}

// Usage
const invoices = await syncInvoices();
console.log(`Fetched ${invoices.length} invoices`);


Summary

The Rillet API is well-designed for a relatively new platform. A few things stand out: the OpenAPI-first approach means you can generate typed clients, updated_at timestamps across most entities make incremental sync viable (check entity-by-entity which ones support updated.gt filtering), and the MCP server is a genuinely useful addition for teams using AI coding tools.

The main things to get right from the start are version pinning, idempotency keys on write operations, webhook signature verification, and using a decimal library for monetary arithmetic. Get those four right and the rest is just building out the specific data flows your product needs.

If you need to support Rillet alongside other accounting platforms, Apideck's Unified Accounting API covers the core objects through a single integration. For Rillet-specific depth, the direct API is the way to go.

For the full API reference, see docs.api.rillet.com and the changelog for what's new.