API guard — rate limit per tenant
Two calls from the same tenant hit back-to-back rate limiters sharing one counter scope: the first passes, the second trips the limit and exits via the 429 lane — the multi-tenant guard pattern (see Admin → Tenants for per-tenant overrides).
What’s inside
7 nodes — every type links to its full reference page.
| Step | Node | Type |
|---|---|---|
| API call | Inject | input.inject |
| Rate limit (call 1) | Rate Limit | logic.rate-limit |
| Rate limit (call 2) | Rate Limit | logic.rate-limit |
| Forward call 2 | Edit Fields (Set) | processing.set |
| Serve call 1 | Console | utility.console |
| 429 lane | Console | utility.console |
| Serve call 2 | Console | utility.console |
Import it
- Studio → Import — paste the JSON below (or the URL
/docs/templates/api-guard-rate-limit-per-tenant.json). - Or ask the AI Copilot with the JSON pasted after the phrase:
import this workflow json into a new workflow named "API guard — rate limit per tenant" - Any credentials the nodes need are
${credential.…}references — add them once in Admin → Credentials and the template picks them up. Nothing secret ships in a template.
The workflow document (flowdrome.workflow.v1, 4 node types)
{
"schemaVersion": "flowdrome.workflow.v1",
"id": "",
"name": "API guard — rate limit per tenant",
"version": "0",
"status": "draft",
"nodes": [
{
"id": "seed",
"type": "input.inject",
"label": "API call",
"category": "trigger",
"config": {
"contentType": "application/json",
"payload": {
"tenant": "acme-tenant",
"call": "GET /v1/reports"
}
},
"position": {
"x": 0,
"y": 46
}
},
{
"id": "first",
"type": "logic.rate-limit",
"label": "Rate limit (call 1)",
"category": "flow",
"config": {
"tenantHeader": "x-tenant-id",
"tenantPath": "tenant",
"missingTenant": "anonymous",
"tenants": "",
"unknownTenant": "deny",
"perSecond": 0,
"perMinute": 0,
"perHour": 1,
"perDay": 0,
"allowCount": 0,
"allowEverySeconds": 0,
"scope": "nd-guard-mrsqq3mu",
"queueName": "",
"queueBroker": "",
"queueToken": "",
"maxWaitMs": 60000,
"storageBackend": "",
"storageHost": "",
"storagePort": 6379,
"storagePassword": "",
"storageDb": 0,
"storageTls": false,
"credentialId": "",
"timeoutMs": 90000
},
"position": {
"x": 530,
"y": 46
}
},
{
"id": "second",
"type": "logic.rate-limit",
"label": "Rate limit (call 2)",
"category": "flow",
"config": {
"tenantHeader": "x-tenant-id",
"tenantPath": "request.tenant",
"missingTenant": "anonymous",
"tenants": "",
"unknownTenant": "deny",
"perSecond": 0,
"perMinute": 0,
"perHour": 1,
"perDay": 0,
"allowCount": 0,
"allowEverySeconds": 0,
"scope": "nd-guard-mrsqq3mu",
"queueName": "",
"queueBroker": "",
"queueToken": "",
"maxWaitMs": 60000,
"storageBackend": "",
"storageHost": "",
"storagePort": 6379,
"storagePassword": "",
"storageDb": 0,
"storageTls": false,
"credentialId": "",
"timeoutMs": 90000
},
"position": {
"x": 2120,
"y": 23
}
},
{
"id": "keep",
"type": "processing.set",
"label": "Forward call 2",
"category": "processing",
"config": {
"mode": "manual",
"assignments": [
{
"name": "request.tenant",
"type": "string",
"value": "{{ $json.tenant }}"
}
],
"include": "all",
"dotNotation": true,
"ignoreConversionErrors": false
},
"position": {
"x": 1590,
"y": 23
}
},
{
"id": "pass1",
"type": "utility.console",
"label": "Serve call 1",
"category": "utility",
"config": {
"message": "call 1 → 200 OK"
},
"position": {
"x": 1060,
"y": 23
}
},
{
"id": "limited",
"type": "utility.console",
"label": "429 lane",
"category": "utility",
"config": {
"message": "call 2 → 429 rate_limited: {{ $json.message }}"
},
"position": {
"x": 2650,
"y": 178
}
},
{
"id": "pass2",
"type": "utility.console",
"label": "Serve call 2",
"category": "utility",
"config": {
"message": "call 2 → 200 OK (unexpected)"
},
"position": {
"x": 2650,
"y": 0
}
}
],
"edges": [
{
"id": "e0",
"sourceNodeId": "seed",
"sourcePort": "output",
"targetNodeId": "first",
"targetPort": "input"
},
{
"id": "e1",
"sourceNodeId": "first",
"sourcePort": "output",
"targetNodeId": "pass1",
"targetPort": "input"
},
{
"id": "e2",
"sourceNodeId": "pass1",
"sourcePort": "output",
"targetNodeId": "keep",
"targetPort": "input"
},
{
"id": "e3",
"sourceNodeId": "keep",
"sourcePort": "output",
"targetNodeId": "second",
"targetPort": "input"
},
{
"id": "e4",
"sourceNodeId": "second",
"sourcePort": "output",
"targetNodeId": "pass2",
"targetPort": "input"
},
{
"id": "e5",
"sourceNodeId": "second",
"sourcePort": "limited",
"targetNodeId": "limited",
"targetPort": "input"
},
{
"id": "e6",
"sourceNodeId": "first",
"sourcePort": "limited",
"targetNodeId": "limited",
"targetPort": "input"
}
],
"triggers": [
{
"id": "t0",
"nodeId": "seed"
}
],
"meta": {
"snippets": [],
"source": "template-gallery",
"description": "Two calls from the same tenant hit back-to-back rate limiters sharing one counter scope: the first passes, the second trips the limit and exits via the 429 lane — the multi-tenant guard pattern (see Admin → Tenants for per-tenant overrides)."
}
} Generated from the verified demo corpus — this exact document seeds and runs on a fresh Flowdrome. Raw JSON: api-guard-rate-limit-per-tenant.json.