feat: license handshake and x-descope-license header#1537
Conversation
Adds a mgmt.license.get() endpoint that calls /v1/mgmt/license and returns the rate limit tier. On client init (when a management key is configured), the SDK runs a fire-and-forget handshake in a daemon thread to cache the tier on the HTTP client. Subsequent management requests carry the cached value in the x-descope-license header so Cloudflare can apply the correct rate limit bucket per customer tier. Tier values: tier1 (free), tier2 (pro), tier3 (growth), tier4 (enterprise). Handshake failure is non-fatal, the SDK continues without the header. The backend interceptor skips license-header validation for the GetLicense endpoint itself, so the initial fetch is safe before the tier is cached. Ref: descope/etc#14245
Coverage reportThe coverage rate went from
Diff Coverage details (click to unfold)descope/mgmt.py
descope/descope_client.py
descope/management/common.py
descope/http_client.py
descope/management/license.py
|
There was a problem hiding this comment.
Pull request overview
Adds management license support and a client-side handshake to cache the project’s rate-limit tier for management requests.
Changes:
- Adds
mgmt.license.get()forGET /v1/mgmt/license. - Starts a background license handshake when a management key is configured.
- Injects cached tiers into the
x-descope-licensemanagement request header.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
descope/descope_client.py |
Starts the background license handshake and caches the tier. |
descope/http_client.py |
Adds the cached tier to default request headers. |
descope/mgmt.py |
Exposes the new mgmt.license facade. |
descope/management/common.py |
Adds the license endpoint path constant. |
descope/management/license.py |
Implements the management license API wrapper. |
tests/management/test_license.py |
Adds tests for license retrieval and header behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if tier: | ||
| self._mgmt_http_client.rate_limit_tier = tier | ||
| except Exception: | ||
| # Handshake failure is non-fatal, SDK continues without the header. |
Replace the daemon-thread handshake with a synchronous httpx.get on construction, bounded by a 5s timeout. The async fetch left short-lived processes (CLI, cron, serverless cold starts) running without the x-descope-license header, landing them in Cloudflare's default rate limit bucket. The change matches the Go SDK pattern. Replace the silent except with logger.warning so handshake failures are operator-visible.
Summary
Adds license handshake support so Cloudflare can apply the correct rate-limit bucket per customer tier.
mgmt.license.get()(GET/v1/mgmt/license) returning{ rateLimitTier: str }x-descope-licenseheader on every management requestTier values
tier1(free) /tier2(pro) /tier3(growth) /tier4(enterprise)Notes
loggingmodule and the SDK runs without the headerGetLicenseitself, so the initial fetch is safe before the tier is cachedTest plan
tests/management/test_license.pycovers the new endpoint and the header injection logicRef: descope/etc#14245