> ## Documentation Index
> Fetch the complete documentation index at: https://docs.twenty.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Good practices

> How to develop an app that works and scales well across many workspaces.

The Twenty API accepts **500 requests per minute per application registration** for the requests an app makes in its own name: runs with no person behind them (cron, webhooks, install hooks) and clients created with `runAs: 'application'`. The budget is shared by every workspace that installed the app, and requests beyond it are refused with a `429`. Requests made as the person who triggered a run count against that person's limits instead. The practices below keep an app inside that budget as its number of installs grows, and inside the limits of the services it calls.

* **Spread sweep crons with a random delay.** A cron trigger starts in every workspace at the same minute, so every install draws on the shared budget at once. Keep the cron handler to an enqueue, with a random `delayMs` over a large window such as one hour. Job enqueues are also capped at 2,000 per minute per application registration, and a cron tick beyond that cap is skipped in the remaining workspaces, so keep a sweep idempotent and let the next tick catch up.

```ts src/logic-functions/enqueue-nightly-sweep.ts theme={null}
import { defineLogicFunction } from 'twenty-sdk/define';
import { enqueueJobs } from 'twenty-sdk/logic-function';

const NIGHTLY_SWEEP = '3f9d1c02-8a44-4f0e-b1d7-9c2e5a7b4f10';
const SPREAD_WINDOW_MS = 60 * 60 * 1000;

export default defineLogicFunction({
  universalIdentifier: 'b7c2e4d1-0f5a-4c3e-9d8b-6a1f2e3c4d5b',
  name: 'enqueue-nightly-sweep',
  timeoutSeconds: 30,
  cronTriggerSettings: { pattern: '0 3 * * *' },
  handler: async () => {
    await enqueueJobs({
      logicFunctionUniversalIdentifier: NIGHTLY_SWEEP,
      jobs: [{ payload: {} }],
      delayMs: Math.floor(Math.random() * SPREAD_WINDOW_MS),
    });
  },
});
```

```text theme={null}
   pattern "0 3 * * *"                  the server, every minute
                                                  │
              ┌───────────────┬───────────────────┼───────────────┐
              ▼               ▼                   ▼               ▼
        workspace A     workspace B     workspace C   ...   workspace N
          03:00:00        03:00:00        03:00:00            03:00:00
              │               │                   │               │
              └───────────────┴─────────┬─────────┴───────────────┘
                                        ▼
                      500 requests per minute for the whole app
                                   N × calls  →  429
```

With a random delay over a one-hour window, the same runs are spread out:

```text theme={null}
   pattern "0 3 * * *"  +  random delayMs in [0, 60 min)
                                                  │
              ┌───────────────┬───────────────────┼───────────────┐
              ▼               ▼                   ▼               ▼
        workspace A     workspace B     workspace C   ...   workspace N
          03:00:00        03:00:00        03:00:00            03:00:00
           + 12 min        + 41 min        + 03 min            + 27 min
              │               │                   │               │
              ▼               ▼                   ▼               ▼
          03:12:00        03:41:00        03:03:00            03:27:00
              │               │                   │               │
              └───────────────┴─────────┬─────────┴───────────────┘
                                        ▼
                      500 requests per minute for the whole app
                          N × calls spread over 60 minutes  →  OK
```

* **Batch database event triggers.** A bulk write such as a mailbox sync emits one event per record, thousands within seconds, and one run per event means one request per record. Set `batchMode: true`, declare `updatedFields`, and reduce the batch to distinct records before calling the API. See [Batching database events](/developers/extend/apps/logic/logic-functions#batching-database-events).

* **Make one request per batch, not per record.** The limit counts requests, not records: a `createMany` with `upsert: true` carrying 200 records is one request, 200 single updates are 200. Read with `in` filters and write with `createMany` or `updateMany`.

* **Collapse bursts into one delayed job.** When events reveal work larger than the event itself, enqueuing it once per event multiplies it by the size of the burst. Enqueue one job with `delayMs` and a deterministic `jobId` that includes a time window; the queue ignores an id it already holds. See [Choose your own job id](/developers/extend/apps/logic/background-jobs#choose-your-own-job-id).

* **Pace fan-outs with `delayMs`.** Jobs enqueued without a delay all become eligible at once and spend the budget in the same minute. Group them into slots per minute and delay the next page by the slots used. See [Background jobs](/developers/extend/apps/logic/background-jobs).

* **Retry a `429` with backoff, or re-enqueue with a delay.** The client SDK throws on any non-2xx response, and a run that waits inside spends its own timeout. Retry with exponential backoff and a cap on attempts, or hand the work back to the queue with a delay.

* **Cache what does not change.** A lookup on every event costs a request for a value fixed at connection time, such as an identity or a team id. Store it in the [key-value store](/developers/extend/apps/logic/key-value-store) when the connection is registered.

* **Batch requests to external APIs.** An external service has its own rate limit and a logic function has a timeout, so `n` requests per run multiply both. When the provider offers a batch endpoint, send one request for the whole batch instead of one per record, and chunk the batch to the provider's maximum.

* **Make default parameters editable by the user.** A value hardcoded in a logic function or a workflow, such as a threshold, a batch size or a default option, fits one workspace and forces a new release for every other. Declare it as an application variable with a default and read it at run time, so workspace admins can adjust it from the app settings. See [Application config](/developers/extend/apps/config/application).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.