Skip to main content

Optimize Your API Calls: Best Practices

The Boond API is governed by call limits. This article compiles best practices to optimize your usage: make only the necessary calls, avoid unnecessary requests, and build an efficient long-term integration.

Written by Charlie Troccaz

🤖 This article was translated by AI.

Who Is This Article For?

For users and integrators who consume the Boond API from a script, connector, or third-party automation tool, and who want to optimize their number of calls—both to stay within limits and to build a cleaner integration.

1. Rate Limiting and Quota

Your usage is governed by two independent mechanisms that are cumulative:

  • Rate limiting smooths your throughput in the short term, per minute and per 10-second interval. If you go too fast, the API temporarily responds with 429 Too Many Requests. The thresholds depend on the endpoint called. This mechanism is being rolled out and will apply from the end of September 2026: size your integration accordingly now.

  • Quota caps your total number of calls per month; this monthly cap depends on your plan.

Threshold details are described in the dedicated article: Understanding API Call Limits.

Some clarifications on what is counted:

  • Only API calls are counted: your employees' navigation in the Boond interface does not count toward the quota.

  • A failed call is counted like any other—the counter increments at authentication, even before your request is processed. An integration that endlessly retries rejected calls will consume its quota for nothing.

  • Calls from Apps developed by third parties (such as partners) do not consume your quota as long as the connection mode uses OAuth2 or JWTApp. If you share login/password information or your admin client key, their calls will be counted.

If you exceed your quota and your volume is not covered by your current plan, you can purchase additional API packs.

2. Reduce Unnecessary Calls

The first source of savings is the calls you can simply avoid making. And it is more valuable than it seems: each call replays the entire authentication sequence—token validation, instance resolution, account and permissions loading, quota counting. Nothing is shared from one call to the next. Therefore, the number of calls, even more than their content, is what matters.

Cache what changes infrequently. Some data is not expected to change often: reference data, configuration, your instance structure. Retrieve it once, store it on your side, and refresh it at a reasonable frequency rather than on every execution.

⚠️ Note: The API does not expose a revalidation mechanism—no ETag, no "nothing has changed" response. Do not rely on the Last-Modified header either: it is returned with the response time, not the data's modification date—relying on it would make you think everything has changed with every call. The last modification date is found in the response itself, on the record. Your cache must therefore rely on a fixed lifetime or on webhooks (see §3).

One useful exception: the response for your instance parameters includes a version number, which changes as soon as the configuration is modified. This is the only reliable signal to detect that a reference has changed without comparing everything.

Save static data. Historical data that no longer changes—old Activity Reports, closed invoices, completed projects—does not need to be re-extracted in a loop. Save it once in your system, then only recall recent or potentially changing periods.

Do not authenticate at every step. A common and costly reflex is to call a verification endpoint like current-user before each request. Authentication does not change during a process: a single check at the beginning of the session is enough.

Use a dedicated account for your integration if it is backend-integrated. Running a flow on a named employee account poses two problems:

  1. The flow inherits their perimeter: all records they are not allowed to see generate rejected calls, which are counted and retried each cycle.

  2. If this employee changes roles or leaves the company, the integration does not stop—it continues with less data visibility, without any visible error.

A service account, with an explicitly defined perimeter, avoids both issues. If you use an integrated App or one relying on the rights of the logged-in user, a service account makes no sense and would circumvent Boond's rights configurations.

Do not request what has already been denied. An access denial is not a transient incident: the record will remain inaccessible until permissions change. Remember the denial and stop requesting it each time. This is a frequent and costly reason: tens of thousands of calls per month, all counted, for a known response.

Retrieve more data per call—but ask yourself the right question first. The number of results per page is adjustable, and leaving it at the default value mechanically increases your calls. Two caveats, however:

  • An out-of-range value is not adjusted to the maximum: it silently falls back to the default value. Check the actual number of results returned rather than assuming your parameter was accepted.

  • Increasing the page size reduces the number of calls, not the cost of the request. For heavy collections, a very large page can be slower than several medium ones, possibly exceeding your timeout—and a call you interrupt is still executed and counted.

Most importantly, if you retrieve a large volume only to extract a list of IDs and then recall each record individually, you need to rethink the mechanism: you are paying twice. Consider what your process truly needs before adjusting page size.

Group rather than one call per record. Sub-resources of a record are efficient for consulting one; used to browse all your records, they become the worst reason—one call per entity, retried each cycle. This is by far the biggest contributor to observed consumption. Whenever a search allows you to retrieve multiple known entities in a single call by listing their references, always prefer it—and apply this reflex everywhere, not just where you first implemented it.

Avoid "just in case" calls. Every call should meet a real need. If you do not know whether data has changed, target the change (see §3) rather than re-downloading everything as a precaution.

3. Synchronize on Changes

For data that changes, the rule is simple: retrieve only what has changed since your last check. Several options, from simplest to most advanced.

Limit your extractions to the relevant window. An unbounded extraction—"everything, every time"—needlessly increases your consumption. Always restrict to the window you actually need, filtering by date, period, or perimeter. An overly broad window not only increases volume: it overflows pages, and you end up paying in pagination for data you do not use. A significant portion of calls in a poorly bounded integration exist only for this reason.

💡 Tip: To build any filtered call (extraction or activity log), apply the desired filter directly in the Boond interface, then open your browser's Network tab: you will see the exact endpoint and parameters, ready to reproduce in your integration.

Switch to incremental. In practice: remember the date of your last run and process only the delta—what has been created or modified since. This often greatly reduces call volume.

Consider the frequency. This is the most effective and often overlooked lever: before optimizing your calls, ask yourself how many times per day you really need to make them. Running a full cycle every hour on Activity Reports, absences, or projects—which do not change fourteen times a day—multiplies consumption for no benefit. Align the cadence with the freshness your use case requires, not what your scheduler allows.

⚠️ Attention: Not all data can be synchronized by delta. Times, absences, and activity reports do not offer a filter by modification date: for these collections, there is no "give me what has changed". The best practice is to strongly restrict the requested window—the current and previous week rather than the whole year—and supplement with the activity log to spot older changes.

Target precisely via the activity log. It lists creations, modifications, and deletions. When queried with filters, it tells you which records have changed, and often which fields, over a given period—you then only need to re-fetch those, instead of everything.

Or switch to event-driven with webhooks. Subscribe to creation, update, and deletion events for the entities you care about: Boond notifies you in real time, instead of you polling repeatedly.

⚠️ Attention: Webhook delivery is "best-effort"—short delay, no retry. If your data freshness relies solely on them, a missed event can create a lasting and silent gap. Plan a daily safety net: a catch-up pass on the activity log, or a delta resync on a sliding 24- to 48-hour window. This is also the only way to see deletions: a deleted record disappears from results without any signal, and a sync by modification date will never tell you.

4. Control Your Throughput

Space out heavy extractions. Extraction, reporting, and export endpoints are among the most costly, with the strictest limits. Reserve them for scheduled and spaced-out processing—off-peak hours, batching—rather than for frequent series.

Request compression. API responses are compressed, but only if your client requests it via the Accept-Encoding: gzip, deflate header. Without it, everything is sent uncompressed, several times larger. This is a single configuration line in your HTTP client, and one of the quickest wins—make sure your library does not remove it.

Reporting cache is decided with your client. Reporting endpoints recalculate all their key indicators by default on each call: these are the most costly in the API. They can serve cached results, but this is not a simple request parameter: the option is set in the instance configuration, is disabled by default, and its retention period—several days—is set by the client. Agree with them on activation and duration: the gain is significant, provided you accept the corresponding freshness.

Handle a 429 properly. This is not a blocking error, but a temporary signal:

  • Respect the Retry-After header: it tells you how many seconds to wait before retrying. Never retry immediately.

  • Apply progressive back-off in the absence of another signal—1s, 2s, 4s, with some randomness—to avoid all your calls restarting at once.

  • Even out your throughput on the client side, with a queue and a ceiling below the limit, rather than sending bursts.

By applying these principles, your integration makes fewer calls, updates faster, and helps preserve platform stability.

Did this answer your question?