{"version":"1788813811057","document":{"id":"f93886b9-50cc-4815-9577-271501f816d6","title":"shop-api: Rate limiting for the public API","owner":"4gVuO8jOTVRrvthbv3i2wTjKxrW2","rootNodeId":"8795424e-c9a7-4586-a4d5-09217e3b19d8","schemaVersion":4,"createdAt":1788813748962,"updatedAt":null},"node":{"canvasWidth":1920,"content":"","id":"8795424e-c9a7-4586-a4d5-09217e3b19d8","canvasHeight":1080,"title":"shop-api: Rate limiting for the public API","elementWriters":{},"elements":{"0daec05e-b643-433b-9e4a-2b52096d186d":{"id":"0daec05e-b643-433b-9e4a-2b52096d186d","elementType":"text","x":60,"y":40,"width":1200,"height":60,"rotation":0,"schemaVersion":1,"properties":{"text":"# Rate limiting for the public API","fontSize":28,"fillColor":"#0f172a","__usesTopLeft":true},"zOrder":0},"b0d211e6-81a4-48e5-a444-cb61cd4e3449":{"id":"b0d211e6-81a4-48e5-a444-cb61cd4e3449","elementType":"text","x":1500,"y":50,"width":360,"height":40,"rotation":0,"schemaVersion":1,"properties":{"text":"**Status:** 📋 PROPOSED","fontSize":18,"fillColor":"#0f172a","__usesTopLeft":true},"zOrder":1},"8a177f12-cb7a-43c9-9d87-b56207b17043":{"id":"8a177f12-cb7a-43c9-9d87-b56207b17043","elementType":"text","x":60,"y":110,"width":1400,"height":60,"rotation":0,"schemaVersion":1,"properties":{"text":"Per-key token buckets in front of `/v1/*`, shared across replicas, with a client contract and a staged rollout. Click a phase box to open its checklist.","fontSize":15,"fillColor":"#0f172a","__usesTopLeft":true},"zOrder":2},"c24387da-e4d7-4485-95b7-936d4152e146":{"id":"c24387da-e4d7-4485-95b7-936d4152e146","elementType":"rectangle","x":60,"y":240,"width":380,"height":130,"rotation":0,"schemaVersion":2,"properties":{"text":"**Phase 1**\n\nToken-bucket middleware\n(in-process, behind a flag)","fillColor":"#E0F2FE","strokeColor":"#334155","strokeWidth":2,"textAlign":"center","verticalAlign":"middle","fontSize":17,"textColor":"#0f172a","cornerRadius":0},"isLink":true,"linkTarget":"f3626488-d812-4bc1-bb79-367444f3c991","linkOrder":1,"zOrder":3},"28123ac8-2d91-4b3b-8ebe-4fff05e04588":{"id":"28123ac8-2d91-4b3b-8ebe-4fff05e04588","elementType":"rectangle","x":520,"y":240,"width":380,"height":130,"rotation":0,"schemaVersion":2,"properties":{"text":"**Phase 2**\n\nRedis-backed counters\n(shared across replicas)","fillColor":"#FEF3C7","strokeColor":"#334155","strokeWidth":2,"textAlign":"center","verticalAlign":"middle","fontSize":17,"textColor":"#0f172a","cornerRadius":0},"isLink":true,"linkTarget":"62053593-b48e-4bd5-b0b6-facdb93e49f9","linkOrder":2,"zOrder":4},"d40daa6e-878d-4bc4-8a4d-aba37c1fe2f9":{"id":"d40daa6e-878d-4bc4-8a4d-aba37c1fe2f9","elementType":"rectangle","x":980,"y":240,"width":380,"height":130,"rotation":0,"schemaVersion":2,"properties":{"text":"**Phase 3**\n\nClient contract\n(headers, 429, SDKs)","fillColor":"#EDE9FE","strokeColor":"#334155","strokeWidth":2,"textAlign":"center","verticalAlign":"middle","fontSize":17,"textColor":"#0f172a","cornerRadius":0},"isLink":true,"linkTarget":"ead01411-0e6f-49cb-8b68-5ceda586a7d6","linkOrder":3,"zOrder":5},"913c1914-18e3-49f4-8901-d36792e4c021":{"id":"913c1914-18e3-49f4-8901-d36792e4c021","elementType":"rectangle","x":1440,"y":240,"width":380,"height":130,"rotation":0,"schemaVersion":2,"properties":{"text":"**Phase 4**\n\nRollout\n(shadow mode, tiers, flag removal)","fillColor":"#DCFCE7","strokeColor":"#334155","strokeWidth":2,"textAlign":"center","verticalAlign":"middle","fontSize":17,"textColor":"#0f172a","cornerRadius":0},"isLink":true,"linkTarget":"60ffafa0-ac06-4ee4-ac25-b83ef46ccc6d","linkOrder":4,"zOrder":6},"35525996-856a-4d99-a207-7005b12eba58":{"id":"35525996-856a-4d99-a207-7005b12eba58","elementType":"uml-note","x":60,"y":450,"width":760,"height":300,"rotation":0,"schemaVersion":2,"properties":{"colorVariant":"sky","fillColor":"#A8D8F0","strokeColor":"#000000","strokeWidth":1,"textColor":"#000000","fontSize":14,"textAlign":"left","verticalAlign":"top","text":"**Goal**\n\nProtect the public API from runaway clients without touching well-behaved ones: 600 requests/min per key with a burst of 100, the same numbers on every replica, and a 429 that tells the client exactly when to retry.\n\n**Out of scope**\n\n- Per-tenant quotas (billing owns those)\n- Internal service-to-service traffic"},"zOrder":7},"ac6049d6-2881-4d00-adad-232dbd5ddc2a":{"id":"ac6049d6-2881-4d00-adad-232dbd5ddc2a","elementType":"uml-note","x":880,"y":450,"width":940,"height":300,"rotation":0,"schemaVersion":2,"properties":{"colorVariant":"peach","fillColor":"#FFB4A2","strokeColor":"#000000","strokeWidth":1,"textColor":"#000000","fontSize":14,"textAlign":"left","verticalAlign":"top","text":"**Risks / open questions**\n\n- **Fail-open or fail-closed** when Redis is unreachable? Plan says open; security may want closed for auth routes.\n- **Clock drift** between replicas skews refill; the Lua script uses Redis time, not host time.\n- **Shared IPs** (corporate NAT) can starve anonymous callers; keyed limits first, IP limits generous.\n- **SDK release cadence**: Phase 3 needs both SDKs cut before Phase 4 enforces."},"zOrder":8},"31d7db59-7043-4f44-9bab-3e0b580ef9db":{"id":"31d7db59-7043-4f44-9bab-3e0b580ef9db","elementType":"connector","schemaVersion":1,"properties":{"startElementId":"c24387da-e4d7-4485-95b7-936d4152e146","endElementId":"28123ac8-2d91-4b3b-8ebe-4fff05e04588","startAnchor":"edge-right","endAnchor":"edge-left","strokeColor":"#334155","strokeWidth":2,"lineStyle":"solid","startMarker":"circle","endMarker":"arrow","label":""},"zOrder":9},"e5c6a4f5-ff83-4be1-8024-93ef9cca844e":{"id":"e5c6a4f5-ff83-4be1-8024-93ef9cca844e","elementType":"connector","schemaVersion":1,"properties":{"startElementId":"28123ac8-2d91-4b3b-8ebe-4fff05e04588","endElementId":"d40daa6e-878d-4bc4-8a4d-aba37c1fe2f9","startAnchor":"edge-right","endAnchor":"edge-left","strokeColor":"#334155","strokeWidth":2,"lineStyle":"solid","startMarker":"circle","endMarker":"arrow","label":""},"zOrder":10},"302f68f7-02f1-4a5e-bd44-72c2db7cfcd3":{"id":"302f68f7-02f1-4a5e-bd44-72c2db7cfcd3","elementType":"connector","schemaVersion":1,"properties":{"startElementId":"d40daa6e-878d-4bc4-8a4d-aba37c1fe2f9","endElementId":"913c1914-18e3-49f4-8901-d36792e4c021","startAnchor":"edge-right","endAnchor":"edge-left","strokeColor":"#334155","strokeWidth":2,"lineStyle":"solid","startMarker":"circle","endMarker":"arrow","label":""},"zOrder":11}}}}