Python SDK for the qURL™ API — secure, time-limited access links. Quantum URL is how you enter the hidden layer of the internet.
Project description
qurl-python
Python SDK for the qURL™ API — secure, time-limited access links for AI agents.
Quantum URL (qURL) · The internet has a hidden layer. This is how you enter.
Why qURL?
AI agents need to access APIs, databases, and internal tools — but permanent credentials are a security risk. qURL creates time-limited, auditable access links that automatically expire:
- Time-limited — links expire after minutes, hours, or days
- IP-scoped — access is granted only to the requesting IP via NHP
- Auditable — every access is logged with who, when, and from where
- Revocable — kill access instantly if something goes wrong
Installation
pip install qurl-python
For LangChain integration:
pip install qurl-python[langchain]
Quick Start
from layerv_qurl import QURLClient
client = QURLClient(api_key="lv_live_xxx")
# Create a protected link
result = client.create(
target_url="https://api.example.com/data",
expires_in="24h",
label="API access for agent",
)
print(result.qurl_link) # Share this link
# Resolve a token (grants network access for your IP)
access = client.resolve("at_k8xqp9h2sj9lx7r4a")
print(f"Access granted to {access.target_url} for {access.access_grant.expires_in}s")
# Extend a qURL's expiration
qurl = client.extend("r_xxx", "7d")
# Update resource metadata
qurl = client.update("r_xxx", description="extended", extend_by="7d")
Authentication Notes
QURLClient(api_key=...) accepts either a qURL API key or a JWT bearer token.
Dashboard/account endpoints such as billing, customer, connector, webhook, and
API-key management require JWT authentication. You may omit api_key only for
public endpoints such as access-code redemption; authenticated endpoints return
401 without credentials.
Some resource-list responses intentionally omit target_url for redacted
resource types. Treat QURL.target_url as str | None before formatting or
parsing it.
Mutating SDK methods generate a per-call Idempotency-Key when you do not
provide one and reuse it across the client's internal retries; qurl-service
supports that header on mutating endpoints, including POST /v1/resolve.
Automatic POST status-code retries remain limited to rate limits because
one-time resolve tokens can be consumed by server-side knock failures. Pass a
stable idempotency_key when you need retry-safe behavior across your own retry
loop, process restart, or job replay. Caller-supplied keys should be globally
unique for each logical operation; UUID or ULID values are recommended.
Fields such as webhook events and API-key scopes accept ordered non-string
iterables of strings. Lists, tuples, and generators preserve the caller's
iteration order; sets are rejected because their iteration order is not stable.
Async Usage
import asyncio
from layerv_qurl import AsyncQURLClient
async def main():
async with AsyncQURLClient(api_key="lv_live_xxx") as client:
result = await client.create(target_url="https://example.com", expires_in="1h")
access = await client.resolve("at_...")
# Extend expiration
qurl = await client.extend("r_xxx", "7d")
asyncio.run(main())
Pagination
# Iterate all active qURLs (auto-paginates)
for qurl in client.list_all(status="active"):
target = qurl.target_url or "<redacted>"
print(f"{qurl.resource_id}: {target}")
# Or fetch a single page
page = client.list(status="active", limit=10)
for qurl in page.qurls:
print(qurl.resource_id)
Resources
# Create a resource explicitly, then mint scoped qURLs against it
resource = client.create_resource(
resource_type="url",
target_url="https://api.example.com/data",
alias="reports-api",
)
link = client.create_qurl_for_resource(
resource.resource_id,
expires_in="1h",
label="Alice from Acme",
idempotency_key="invite-alice-2026-03-10",
)
# Revoke one token without closing the whole resource
assert link.qurl_id is not None
client.revoke_resource_qurl(resource.resource_id, link.qurl_id)
Custom Domains And Webhooks
domain = client.register_domain("secure.example.com")
for record in domain.dns_records:
print(record.type, record.name, record.value)
webhook = client.create_webhook(
url="https://example.com/qurl-webhooks",
events=["qurl.accessed", "domain.verified"],
)
print(webhook.secret) # Returned only on create/regenerate
Error Handling
Every API error maps to a specific exception class, so you can catch exactly what you need:
from layerv_qurl import (
QURLClient,
QURLError,
QURLNetworkError,
QURLTimeoutError,
)
from layerv_qurl.errors import (
AuthenticationError,
AuthorizationError,
NotFoundError,
RateLimitError,
ValidationError,
)
client = QURLClient(api_key="lv_live_xxx")
try:
client.resolve("at_k8xqp9h2sj9lx7r4a")
except AuthenticationError:
print("Bad API key")
except AuthorizationError:
print("Valid key but missing qurl:resolve scope")
except NotFoundError:
print("Token doesn't exist or already expired")
except RateLimitError as e:
print(f"Rate limited — retry in {e.retry_after}s")
except ValidationError as e:
print(f"Bad request: {e.detail}")
if e.invalid_fields:
for field, reason in e.invalid_fields.items():
print(f" {field}: {reason}")
except QURLTimeoutError:
print("Request timed out")
except QURLNetworkError as e:
print(f"Network error: {e}")
except QURLError as e:
# Catch-all for any other API error
print(f"API error {e.status}: {e.detail}")
All error classes inherit from QURLError, so except QURLError catches everything.
Typed Quota
quota = client.get_quota()
print(f"Plan: {quota.plan}")
print(f"Active qURLs: {quota.usage.active_qurls}")
print(f"Rate limit: {quota.rate_limits.create_per_minute}/min")
JWT-authenticated dashboard endpoints are also available for usage, customer settings, billing sessions, invoices, and API-key management. API-key auth continues to work for normal qURL, resource, domain, webhook, connector, and access-code operations according to the API scopes on the key.
Debug Logging
Enable debug logs to see every request and retry:
import logging
logging.getLogger("layerv_qurl").setLevel(logging.DEBUG)
# Output:
# DEBUG:layerv_qurl:POST https://api.layerv.ai/v1/qurl
# DEBUG:layerv_qurl:POST https://api.layerv.ai/v1/qurl → 201
LangChain Integration
from layerv_qurl import QURLClient
from layerv_qurl.langchain import QURLToolkit
client = QURLClient(api_key="lv_live_xxx")
toolkit = QURLToolkit(client=client)
tools = toolkit.get_tools() # [CreateQURLTool, ResolveQURLTool, ListQURLsTool, DeleteQURLTool]
Configuration
| Parameter | Required | Default |
|---|---|---|
api_key |
Yes | — |
base_url |
No | https://api.layerv.ai |
timeout |
No | 30.0 |
max_retries |
No | 3 |
user_agent |
No | qurl-python-sdk/<version> |
http_client |
No | Auto-created httpx.Client |
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file qurl_python-0.2.1.tar.gz.
File metadata
- Download URL: qurl_python-0.2.1.tar.gz
- Upload date:
- Size: 89.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a0f95f300e39ebfb33c3c2d63b0750b2591df09329bc873b01a5ca950ec67674
|
|
| MD5 |
9e96c6e5be3b4e57c64bea6ee52edcf5
|
|
| BLAKE2b-256 |
3872c353f1d5a3c3897f450e8e0f4cfabc3eccdcb7321ff03a8715524d21c132
|
Provenance
The following attestation bundles were made for qurl_python-0.2.1.tar.gz:
Publisher:
release-please.yml on layervai/qurl-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
qurl_python-0.2.1.tar.gz -
Subject digest:
a0f95f300e39ebfb33c3c2d63b0750b2591df09329bc873b01a5ca950ec67674 - Sigstore transparency entry: 2000948637
- Sigstore integration time:
-
Permalink:
layervai/qurl-python@ecd46a2c161dcbeaf1f4b2a8eff2e599250863cc -
Branch / Tag:
refs/heads/main - Owner: https://github.com/layervai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-please.yml@ecd46a2c161dcbeaf1f4b2a8eff2e599250863cc -
Trigger Event:
push
-
Statement type:
File details
Details for the file qurl_python-0.2.1-py3-none-any.whl.
File metadata
- Download URL: qurl_python-0.2.1-py3-none-any.whl
- Upload date:
- Size: 53.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2193de7a54dafc91087ad00f68bce56c0ffa1a1a13497ccb78c685d6d6352d8f
|
|
| MD5 |
9b9e07251d65cf758fe62c41ed6125fd
|
|
| BLAKE2b-256 |
f46386e69d62694b3e2d05c04bf23ec36d89f8efc91762d7ef67355c08224f11
|
Provenance
The following attestation bundles were made for qurl_python-0.2.1-py3-none-any.whl:
Publisher:
release-please.yml on layervai/qurl-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
qurl_python-0.2.1-py3-none-any.whl -
Subject digest:
2193de7a54dafc91087ad00f68bce56c0ffa1a1a13497ccb78c685d6d6352d8f - Sigstore transparency entry: 2000948879
- Sigstore integration time:
-
Permalink:
layervai/qurl-python@ecd46a2c161dcbeaf1f4b2a8eff2e599250863cc -
Branch / Tag:
refs/heads/main - Owner: https://github.com/layervai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-please.yml@ecd46a2c161dcbeaf1f4b2a8eff2e599250863cc -
Trigger Event:
push
-
Statement type: