{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-tutorials/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["admonition"]},"type":"markdown"},"seo":{"title":"M2M Token Limits"},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"m2m-token-limits","__idx":0},"children":["M2M Token Limits"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Access tokens obtained through the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/tutorials/authentication"},"children":["Client Credentials flow"]}," are subject to ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["per-application limits"]}," in addition to your tenant's monthly allotment."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["These limits exist to protect your tenant. The monthly allotment is shared by every integration running against your Rhythm tenant, so a single misbehaving application can exhaust it and leave every other integration unable to authenticate. Per-application limits contain that failure to the application causing it."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"the-limits","__idx":1},"children":["The limits"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Limit"},"children":["Limit"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Default"},"children":["Default"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Window"},"children":["Window"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Per application, per hour"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["2 tokens"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["UTC clock hour"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Per application, per day"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["4 tokens"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["UTC calendar day"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Per tenant, per month"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["1,000 tokens"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["UTC calendar month"]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Windows are ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["calendar-aligned in UTC"]},", not rolling. The hourly window resets at the top of each UTC hour, and the daily window resets at 00:00 UTC — not 24 hours after your first request."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A correctly designed integration requests one token per day and reuses it, so the daily limit is roughly four times what normal operation requires."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"what-a-rejected-request-looks-like","__idx":2},"children":["What a rejected request looks like"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When a limit is exceeded, the token request is rejected and no token is issued:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"error\": \"invalid_request\",\n  \"error_description\": \"M2M access token quota exceeded (max 2 per hour). Access tokens are valid for 24 hours - cache and reuse them.\"\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The description states which limit was reached."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"how-rejections-affect-each-limit","__idx":3},"children":["How rejections affect each limit"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A rejected request affects the two limits differently, and the difference matters:"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":""},"children":[]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Rejected request counts?"},"children":["Rejected request counts?"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Per-application hourly and daily limits"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Yes"]}," — these count requests, not tokens issued"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Your tenant's monthly allotment"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["No"]}," — only tokens actually issued are counted"]}]}]}]}]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"success","name":"Rejections do not consume your monthly allotment"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If an integration requests tokens in a loop and is rejected, those rejections cost you nothing from your monthly allotment. Only the tokens actually issued count against it."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This is deliberate: the per-application limits exist so that one misbehaving integration cannot drain the allotment shared by every other integration on your tenant."]}]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"danger","name":"Do not retry after a quota rejection"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Retrying still works against you. Because the hourly and daily counters record ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["requests"]},", each retry pushes your application further past its limit and delays the point at which it can obtain a token again."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["An integration retrying in a loop typically receives only 2 tokens in a day — fewer than the 4 it would have received by not retrying at all."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Treat a quota rejection as ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["non-retryable"]},". It is not transient and it will not succeed on retry."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This is the opposite of a ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/tutorials/rate-limiting"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["429 Too Many Requests"]}]}," response, where retrying with exponential backoff is the correct behavior. A ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["429"]}," means slow down; a quota rejection means you already hold a token you should be reusing."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"staying-within-the-limits","__idx":4},"children":["Staying within the limits"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Request an access token"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Cache it securely in memory or a server-side store"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Reuse it for every API call until it is close to expiring"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Request a new one only then — tokens are valid for 24 hours"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["If a token request is rejected for quota, stop requesting and surface the error"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Common causes of exceeding the limits:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Requesting a new token on every API call"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Requesting a new token each time a scheduled job runs"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Requesting a new token per end user"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Running several instances or workers that each request their own token without a shared cache"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Retrying automatically after a rejection"]}]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info","name":"Multiple instances"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If your integration runs as several instances, containers or workers, they all share one application's limits — the limits apply to the application, not to the process."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Either share a cached token between them, or request a higher limit for that application."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"requesting-a-higher-limit","__idx":5},"children":["Requesting a higher limit"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Limits are configured per application and can be raised where there is a legitimate need — for example an integration that genuinely requires more than four tokens a day across coordinated workers."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Contact Rhythm support with the application name and your expected token usage."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"summary-checklist","__idx":6},"children":["Summary checklist"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Tokens are cached and reused for their full 24-hour lifetime"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," A single token is shared across instances, or a higher limit has been requested"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Quota rejections are surfaced, not retried"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["429"]}," responses are retried with backoff — quota rejections are not"]}]}]},"headings":[{"value":"M2M Token Limits","id":"m2m-token-limits","depth":1},{"value":"The limits","id":"the-limits","depth":2},{"value":"What a rejected request looks like","id":"what-a-rejected-request-looks-like","depth":2},{"value":"How rejections affect each limit","id":"how-rejections-affect-each-limit","depth":2},{"value":"Staying within the limits","id":"staying-within-the-limits","depth":2},{"value":"Requesting a higher limit","id":"requesting-a-higher-limit","depth":2},{"value":"Summary checklist","id":"summary-checklist","depth":2}],"frontmatter":{"seo":{"title":"M2M Token Limits"}},"lastModified":"2026-10-06T18:39:38.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/tutorials/m2m-token-limits","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}