{"id":47642629,"url":"https://github.com/webexsamples/webexretryafterdemo","last_synced_at":"2026-04-02T01:40:27.741Z","repository":{"id":75823368,"uuid":"64871395","full_name":"WebexSamples/WebexRetryAfterDemo","owner":"WebexSamples","description":"Example reaching the API request rate limit, and handling future requests using the Retry-After header.","archived":false,"fork":false,"pushed_at":"2025-04-16T15:39:24.000Z","size":120,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-05-17T05:13:25.152Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/WebexSamples.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2016-08-03T18:53:30.000Z","updated_at":"2025-04-16T15:39:28.000Z","dependencies_parsed_at":"2025-04-16T17:04:29.879Z","dependency_job_id":"0b6a37a2-e8e1-404f-95ce-272d9eb29824","html_url":"https://github.com/WebexSamples/WebexRetryAfterDemo","commit_stats":null,"previous_names":["webexsamples/webexretryafterdemo","webex/sparkretryafterdemo"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/WebexSamples/WebexRetryAfterDemo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebexSamples%2FWebexRetryAfterDemo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebexSamples%2FWebexRetryAfterDemo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebexSamples%2FWebexRetryAfterDemo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebexSamples%2FWebexRetryAfterDemo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/WebexSamples","download_url":"https://codeload.github.com/WebexSamples/WebexRetryAfterDemo/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebexSamples%2FWebexRetryAfterDemo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31294054,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T01:05:07.454Z","status":"ssl_error","status_checked_at":"2026-04-02T00:56:46.496Z","response_time":53,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-02T01:40:27.165Z","updated_at":"2026-04-02T01:40:27.736Z","avatar_url":"https://github.com/WebexSamples.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ⏱️ Webex Retry Python Demo\n\nSee the following blog post on the Webex Developer Portal for complete details on this application: [Rate Limiting with the Webex API](https://developer.webex.com/blog/rate-limiting-and-the-webex-api).\n\nThis simple Python demonstration shows how to properly handle rate limiting when making requests to the Webex APIs, implementing the Retry-After header pattern for robust API integration.\n\n## ✨ Features\n\n- **🔄 Rate Limit Handling** - Proper implementation of HTTP 429 response handling\n- **⏰ Retry-After Implementation** - Respects server-provided retry delays\n- **📊 Progress Monitoring** - Real-time feedback during sleep periods\n- **🎯 Continuous Testing** - Infinite loop for demonstrating rate limit behavior\n- **📈 Response Tracking** - HTTP status codes and tracking IDs for debugging\n- **⚡ Chunked Sleep** - User-friendly progress updates during long waits\n\n## 🚀 Quick Start\n\n### Prerequisites\n\n- Python 3.x\n- Valid Webex personal access token\n- Internet connection for API calls\n\n### Setup and Run\n\n1. **Clone or download the file:**\n   ```bash\n   wget https://raw.githubusercontent.com/WebexSamples/WebexRetryAfterDemo/main/retryafterdemo.py\n   # or manually download retryafterdemo.py\n   ```\n\n2. **Configure your access token:**\n   ```python\n   # Edit retryafterdemo.py\n   bearer = \"YOUR_PERSONAL_ACCESS_TOKEN_HERE\"\n   ```\n\n3. **Run the demonstration:**\n   ```bash\n   python retryafterdemo.py\n   ```\n\n4. **Observe the output:**\n   - Normal API responses with status codes and tracking IDs\n   - Rate limit detection and Retry-After header handling\n   - Progress updates during sleep periods\n\n## 📖 Usage Guide\n\n### Getting Your Access Token\n\n1. Visit [Webex Developer Portal](https://developer.webex.com/docs/getting-started)\n2. Log in with your Webex account\n3. Copy your personal access token\n4. Replace `PERSONAL_ACCESS_TOKEN` in the code\n\n### Understanding the Output\n\n```bash\n# Normal successful response\n200 1640995200.123456 Y2lzY29zcGFyazovL3VzL1RSQUNLSU5HX0lE\n\n# Rate limit detected\ncode 429\nheaders Server: nginx\nRetry-After: 60\nDate: Thu, 01 Jan 2024 12:00:00 GMT\n...\nSleeping for 60 seconds\nAsleep for 50 more seconds\nAsleep for 40 more seconds\n...\n```\n\n### Expected Behavior\n\n1. **Normal Operation:**\n   - Continuous API calls to `/v1/rooms` endpoint\n   - Display of HTTP 200 responses with timestamps\n   - Tracking ID logging for debugging purposes\n\n2. **Rate Limit Triggered:**\n   - HTTP 429 status code detection\n   - Retry-After header value extraction\n   - Intelligent sleep with progress updates\n   - Automatic resumption after wait period\n\n## 🏗️ Code Implementation\n\n### Core Function Structure\n\n```python\ndef sendWebexGET(url):\n    \"\"\"Send authenticated GET request to Webex API\"\"\"\n    request = urllib.request.Request(url,\n                                     headers={\"Accept\": \"application/json\",\n                                              \"Content-Type\": \"application/json\"})\n    request.add_header(\"Authorization\", \"Bearer \" + bearer)\n    response = urllib.request.urlopen(request)\n    return response\n```\n\n### Rate Limiting Logic\n\n```python\n# Main execution loop\nwhile True:\n    try:\n        result = sendWebexGET('https://webexapis.com/v1/rooms')\n        print(result.getcode(), time.time(), result.headers['Trackingid'])\n    except urllib.error.HTTPError as e:\n        if e.code == 429:\n            # Handle rate limiting\n            print('code', e.code)\n            print('headers', e.headers)\n            print('Sleeping for', e.headers['Retry-After'], 'seconds')\n            \n            # Chunked sleep for better user experience\n            sleep_time = int(e.headers['Retry-After'])\n            while sleep_time \u003e 10:\n                time.sleep(10)\n                sleep_time -= 10\n                print('Asleep for', sleep_time, 'more seconds')\n            time.sleep(sleep_time)\n        else:\n            # Handle other HTTP errors\n            print(e, e.code)\n            break\n```\n\n### Key Implementation Details\n\n| Component | Description | Purpose |\n|-----------|-------------|---------|\n| **Infinite Loop** | `while True:` continuous execution | Demonstrate repeated API calls |\n| **Exception Handling** | `try/except` for HTTP errors | Catch and handle rate limits |\n| **Retry-After Header** | `e.headers['Retry-After']` | Server-specified wait time |\n| **Chunked Sleep** | 10-second intervals with updates | User-friendly progress tracking |\n| **Bearer Authentication** | `Authorization: Bearer` header | Webex API authentication |\n\n## 🔧 Rate Limiting Concepts\n\n### HTTP 429 Response\n\nWhen rate limits are exceeded, Webex APIs return:\n\n```http\nHTTP/1.1 429 Too Many Requests\nRetry-After: 60\nDate: Thu, 01 Jan 2024 12:00:00 GMT\nServer: nginx\nContent-Type: application/json\n\n{\n  \"message\": \"Rate limit exceeded\",\n  \"errors\": [\n    {\n      \"description\": \"Too Many Requests\"\n    }\n  ]\n}\n```\n\n### Retry-After Header Values\n\n| Value Type | Example | Description |\n|------------|---------|-------------|\n| **Seconds** | `60` | Wait 60 seconds before retry |\n| **HTTP Date** | `Thu, 01 Jan 2024 12:01:00 GMT` | Wait until specified time |\n\n### Best Practices Demonstrated\n\n1. **Always Check for 429:** Specifically handle rate limit responses\n2. **Respect Retry-After:** Use server-provided wait times\n3. **Graceful Degradation:** Continue operation after rate limits\n4. **User Feedback:** Provide progress updates during waits\n5. **Error Handling:** Manage other HTTP errors appropriately\n\n## 📊 Rate Limit Patterns\n\n### Webex API Rate Limits\n\n| API Category | Typical Limits | Retry Behavior |\n|--------------|----------------|----------------|\n| **REST APIs** | 300 requests/minute | Exponential backoff |\n| **Admin APIs** | 100 requests/minute | Fixed Retry-After |\n| **Compliance** | 50 requests/minute | Longer wait periods |\n| **Recordings** | 20 requests/minute | Progressive delays |\n\n### Triggering Rate Limits\n\nThe demo will trigger rate limits by:\n- Making continuous requests without delays\n- Exceeding the `/v1/rooms` endpoint limits\n- Demonstrating real-world rate limiting scenarios\n\n### Recovery Patterns\n\n```python\n# Progressive sleep with user feedback\nsleep_time = int(e.headers['Retry-After'])\nwhile sleep_time \u003e 10:\n    time.sleep(10)           # Sleep in 10-second chunks\n    sleep_time -= 10         # Decrement remaining time\n    print('Asleep for', sleep_time, 'more seconds')  # Progress update\ntime.sleep(sleep_time)       # Final sleep for remainder\n```\n\n## 🧪 Testing and Experimentation\n\n### Modifying Request Frequency\n\n```python\n# Add delays to reduce rate limiting\nimport time\nwhile True:\n    try:\n        result = sendWebexGET('https://webexapis.com/v1/rooms')\n        print(result.getcode(), time.time(), result.headers['Trackingid'])\n        time.sleep(2)  # Add 2-second delay between requests\n    except urllib.error.HTTPError as e:\n        # Rate limiting logic...\n```\n\n### Testing Different Endpoints\n\n```python\n# Test various API endpoints\nendpoints = [\n    'https://webexapis.com/v1/rooms',\n    'https://webexapis.com/v1/people/me',\n    'https://webexapis.com/v1/messages'\n]\n\nfor endpoint in endpoints:\n    result = sendWebexGET(endpoint)\n    print(f\"{endpoint}: {result.getcode()}\")\n```\n\n### Monitoring Rate Limit Headers\n\n```python\n# Check rate limit headers in responses\ndef print_rate_limit_info(response):\n    headers = response.headers\n    if 'X-RateLimit-Limit' in headers:\n        print(f\"Rate Limit: {headers['X-RateLimit-Limit']}\")\n    if 'X-RateLimit-Remaining' in headers:\n        print(f\"Remaining: {headers['X-RateLimit-Remaining']}\")\n    if 'X-RateLimit-Reset' in headers:\n        print(f\"Reset Time: {headers['X-RateLimit-Reset']}\")\n```\n\n## 🚨 Troubleshooting\n\n### Common Issues\n\n| Issue | Solution |\n|-------|----------|\n| **Invalid Token** | Replace `PERSONAL_ACCESS_TOKEN` with valid token |\n| **No Rate Limits** | Add more aggressive request patterns or remove delays |\n| **Connection Errors** | Check internet connectivity and API status |\n| **Permission Errors** | Ensure token has appropriate scopes |\n\n### Debug Output\n\n```python\n# Add debug information\ntry:\n    result = sendWebexGET('https://webexapis.com/v1/rooms')\n    print(f\"Success: {result.getcode()} at {time.time()}\")\n    print(f\"Tracking ID: {result.headers.get('Trackingid', 'None')}\")\n    print(f\"Response Headers: {dict(result.headers)}\")\nexcept urllib.error.HTTPError as e:\n    print(f\"HTTP Error: {e.code}\")\n    print(f\"Error Headers: {dict(e.headers)}\")\n    print(f\"Error Message: {e.read().decode()}\")\n```\n\n### Network Considerations\n\n- **Proxy Settings:** Configure urllib for corporate proxies\n- **SSL Verification:** Handle certificate validation if needed\n- **Timeout Settings:** Add request timeouts for reliability\n\n## 📚 Educational Value\n\n### Learning Objectives\n\nThis demo teaches:\n\n1. **HTTP Status Code Handling:** Understanding 429 responses\n2. **Header Processing:** Reading and using Retry-After values\n3. **Exception Management:** Graceful error handling in Python\n4. **API Best Practices:** Respectful rate limit behavior\n5. **User Experience:** Providing feedback during waits\n\n### Real-World Applications\n\nApply these patterns to:\n- **Data Migration:** Bulk operations with rate limiting\n- **Monitoring Tools:** Regular API polling with backoff\n- **Integration Services:** Reliable third-party API usage\n- **Batch Processing:** Large-scale operations with throttling\n\n### Extended Implementations\n\n```python\n# Production-ready rate limiting\nimport time\nimport random\n\nclass RateLimitHandler:\n    def __init__(self, max_retries=3):\n        self.max_retries = max_retries\n    \n    def make_request(self, url):\n        for attempt in range(self.max_retries):\n            try:\n                return sendWebexGET(url)\n            except urllib.error.HTTPError as e:\n                if e.code == 429 and attempt \u003c self.max_retries - 1:\n                    wait_time = int(e.headers.get('Retry-After', 60))\n                    # Add jitter to prevent thundering herd\n                    jitter = random.uniform(0.1, 0.3) * wait_time\n                    time.sleep(wait_time + jitter)\n                else:\n                    raise\n```\n\n## 🤝 Contributing\n\nSuggestions for enhancing this demo:\n\n1. **Additional Error Types:** Handle more HTTP status codes\n2. **Multiple Endpoints:** Test different API rate limits\n3. **Metrics Collection:** Track rate limit patterns\n4. **Configuration Options:** External token management\n5. **Advanced Backoff:** Exponential or jittered strategies\n\n## 📄 License\n\nThis demo is part of the Webex Samples collection and follows the same licensing terms.\n\n## 🆘 Support\n\nFor more information:\n\n- **Blog Post**: [Rate Limiting with the Webex API](https://developer.webex.com/blog/rate-limiting-and-the-webex-api)\n- **API Documentation**: [Webex Rate Limiting](https://developer.webex.com/docs/rate-limiting)\n- **Developer Community**: [Webex Developer Portal](https://developer.webex.com/community)\n\n## Thanks!\n\nMade with ❤️ by the Webex Developer Relations Team at Cisco\n\n---\n\n**Note**: This demo intentionally triggers rate limits for educational purposes. In production applications, implement proper request spacing and rate limit prevention strategies.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebexsamples%2Fwebexretryafterdemo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwebexsamples%2Fwebexretryafterdemo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebexsamples%2Fwebexretryafterdemo/lists"}