# Limits

Self-hosted servers have no limits by default. Operators set them in the `[limits]` section of the configuration; a value of `0` means unlimited. cronpad Cloud applies limits by plan; see [editions](editions.md).

## Monthly runs

A run counts once when it first starts, whether scheduled, manually triggered, or manually retried. Automatic retry attempts, skipped and cancelled occurrences, and `/jobs/test` dry runs do not count. Months are UTC calendar months and reset at midnight UTC on the first day. Deleting or pruning run history does not give quota back.

With `over_quota = "block"`, new occurrences at the cap are recorded as `skipped` with `skip_reason = "quota_exceeded"`. The schedule keeps advancing, so jobs resume next month or after a plan upgrade. With `"allow"`, jobs keep firing and usage continues to count beyond the limit. Manual trigger requests at a blocking cap return `429`:

```json
{"error":{"code":"limit_exceeded","limit":"max_runs_per_month_per_workspace","value":50000}}
```

At 80% and 100% of the monthly limit, workspace owners are emailed (when SMTP is configured) and the `workspace.quota_warning` and `workspace.quota_exceeded` [alert events](../alerts/index.md#events) fire. Each notice is sent once per workspace per month and is not affected by the daily alert-email cap.

`GET /api/v1/usage` returns the current month's usage; see the [Workspace API](../api/workspace.md#usage-and-suspension). If cronpad crashes, the last few milliseconds of usage counts and speculative deliveries (at most 50 ms of work) can be lost, so receivers should honor idempotency keys.

## Concurrency and suspension

`max_concurrent_runs_per_workspace` caps how many runs a workspace executes at once. Excess occurrences wait briefly (250 ms) and are retried without being skipped. Their start lag is visible in run history, but lateness caused by the cap is not counted as an SLA breach.

Operators can suspend a workspace, for example for abuse:

```sh
cronpad workspace suspend <slug> --reason "abuse"
cronpad workspace unsuspend <slug>
```

These commands run against the local database. A suspended workspace stays readable, but API changes return `403 workspace_suspended`, scheduled occurrences are recorded as `skipped` with `skip_reason = "workspace_suspended"`, and alerts and webhook deliveries stop. Both actions are recorded in the audit log.

## Outbound controls

In hosted mode, `sinks.webhook.per_host_rate_limit` applies a shared limit per destination host, default 20 requests/second. Excess requests wait; they are not dropped. Self-hosted mode leaves this off. `max_alert_emails_per_day` suppresses ordinary alert emails after the workspace's daily cap; the next email that is sent reports how many were suppressed. Zero disables either control.

## Other limits

| Setting | Meaning |
|---|---|
| `max_jobs_per_workspace` | Maximum jobs |
| `min_interval` | Minimum interval between occurrences |
| `max_api_keys_per_workspace` | Maximum API keys |
| `max_retry_attempts` | Maximum attempts per run |
| `api_rate_limit_per_minute` | Requests per API key or session |

The deprecated `max_runs_per_day_per_workspace` is still accepted for now. It is converted to a monthly cap by multiplying by 30, with a startup warning; an explicitly configured monthly value takes precedence.

See [Configuration](../self-hosting/configuration.md#limits) and [Data retention](data-retention.md).