nexalayer 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 NexaLayer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,211 @@
1
+ Metadata-Version: 2.4
2
+ Name: nexalayer
3
+ Version: 0.1.0
4
+ Summary: NexaLayer SDK — The Network Execution Layer for AI Agents
5
+ Author: NexaLayer
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://docs.nexalayer.net
8
+ Project-URL: Repository, https://github.com/NexaLayer/nexalayer-sdk
9
+ Project-URL: Issues, https://github.com/NexaLayer/nexalayer-sdk/issues
10
+ Keywords: proxy,agent,session,nexalayer
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: requests>=2.28
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest>=7; extra == "dev"
21
+ Requires-Dist: pytest-cov>=4; extra == "dev"
22
+ Dynamic: license-file
23
+
24
+ # NexaLayer SDK
25
+
26
+ Managed network sessions for Playwright, Browser Use, AI agents, and automation scripts.
27
+
28
+ **Keep using proxies. Stop managing them.**
29
+ 中文:代理继续使用,代理管理交给 NexaLayer。
30
+
31
+ This repository contains the Python and Node/TypeScript SDK source. Release automation publishes signed release artifacts to PyPI and npm after registry ownership and secrets are configured.
32
+
33
+ ## Status
34
+
35
+ | Runtime | Source | Package registry status |
36
+ | --- | --- | --- |
37
+ | Python | `python/` | Package name `nexalayer`; release workflow configured |
38
+ | Node / TypeScript | `node/` | Package name `@nexalayer/sdk`; release workflow configured |
39
+
40
+ ## Installation
41
+
42
+ Python from repository root:
43
+
44
+ ```bash
45
+ python3 -m venv .venv
46
+ . .venv/bin/activate
47
+ pip install -e ".[dev]"
48
+ ```
49
+
50
+ Node from repository root:
51
+
52
+ ```bash
53
+ cd node
54
+ npm install
55
+ npm run build
56
+ ```
57
+
58
+ ## Authentication
59
+
60
+ ```bash
61
+ export NEXALAYER_BASE_URL=https://api.nexalayer.net/v1
62
+ export NEXALAYER_API_KEY=agk_your_key
63
+ ```
64
+
65
+ The SDK uses `X-API-Key` for Agent requests.
66
+
67
+ ## 60-second Python example
68
+
69
+ This example auto-selects a dynamic product, creates a session, reports telemetry, reads health, and terminates the session in cleanup.
70
+
71
+ ```python
72
+ import os
73
+ import time
74
+ import uuid
75
+
76
+ import requests
77
+ from nexalayer import NexaLayerClient
78
+
79
+ api_key = os.environ["NEXALAYER_API_KEY"]
80
+ client = NexaLayerClient(api_key=api_key)
81
+ session_id = None
82
+
83
+ def data(resp):
84
+ return resp.get("data", resp)
85
+
86
+ try:
87
+ products = data(client.get_products(type="dynamic"))
88
+ product = next(p for p in products.get("items", []) if p.get("product_no"))
89
+
90
+ session = client.create_session(
91
+ session_type="dynamic",
92
+ product_no=product["product_no"],
93
+ quantity=1,
94
+ protocol="socks5",
95
+ rotation_mode="on_demand",
96
+ idempotency_key=f"py-{uuid.uuid4()}",
97
+ )
98
+ session_id = session.session_id
99
+
100
+ for _ in range(60):
101
+ current = data(client.get_session(session_id))
102
+ if current.get("status") == "active":
103
+ proxy_url = current["proxy"]["full_url"]
104
+ break
105
+ time.sleep(2)
106
+ else:
107
+ raise TimeoutError("Session did not become active")
108
+
109
+ res = requests.get(
110
+ "https://httpbin.org/ip",
111
+ proxies={"http": proxy_url, "https": proxy_url},
112
+ timeout=30,
113
+ )
114
+ client.report_event(
115
+ session_id,
116
+ event_type="success" if res.ok else "http_error",
117
+ status_code=res.status_code,
118
+ target_host="httpbin.org",
119
+ )
120
+ print(client.get_session_health(session_id))
121
+ finally:
122
+ if session_id:
123
+ client.terminate_session(session_id)
124
+ ```
125
+
126
+ ## Dynamic Session
127
+
128
+ Use a dynamic session when your automation can rotate after errors such as timeout, 403, 429, or explicit blocks.
129
+
130
+ ```python
131
+ session = client.create_session(
132
+ session_type="dynamic",
133
+ product_no="selected_from_products_api",
134
+ quantity=1,
135
+ protocol="socks5",
136
+ rotation_mode="on_demand",
137
+ )
138
+ ```
139
+
140
+ ## Static Session
141
+
142
+ Use a static session when you need fixed network identity for a longer-lived task.
143
+
144
+ ```python
145
+ session = client.create_session(
146
+ session_type="static",
147
+ product_no="selected_from_products_api",
148
+ quantity=1,
149
+ duration=30,
150
+ protocol="socks5",
151
+ )
152
+ ```
153
+
154
+ ## Telemetry
155
+
156
+ ```python
157
+ client.report_event(
158
+ session_id,
159
+ event_type="timeout",
160
+ status_code=0,
161
+ target_host="example.com",
162
+ )
163
+ ```
164
+
165
+ ## Health
166
+
167
+ ```python
168
+ health = client.get_session_health(session_id)
169
+ ```
170
+
171
+ `timeout`, `captcha`, `block`, and repeated HTTP errors are not neutral noise; report them so NexaLayer can score the session and recommend rotate or pause.
172
+
173
+ ## Cleanup
174
+
175
+ Always terminate sessions you no longer need:
176
+
177
+ ```python
178
+ try:
179
+ ...
180
+ finally:
181
+ if session_id:
182
+ client.terminate_session(session_id)
183
+ ```
184
+
185
+ ## Playwright
186
+
187
+ Use the public Playwright examples:
188
+
189
+ - Chinese: https://github.com/NexaLayer/nexalayer-examples/tree/main/playwright/basic-session
190
+ - English: https://github.com/NexaLayer/nexalayer-examples/tree/main/playwright/basic-session
191
+
192
+ ## 中文文档
193
+
194
+ - 中文快速开始: https://docs.nexalayer.net/zh/quick-start
195
+ - Playwright 中文 Demo: https://docs.nexalayer.net/zh/examples
196
+ - API 文档: https://docs.nexalayer.net/api-reference/openapi
197
+
198
+ ## Error handling
199
+
200
+ - Check `backend_ready=true` before creating paid sessions after registration.
201
+ - Use `Idempotency-Key` for create, rotate, renew, and recharge operations.
202
+ - Do not log `proxy.full_url` in production without redaction because it can contain proxy credentials.
203
+ - Respect target website terms and applicable law.
204
+
205
+ ## Contributing
206
+
207
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
208
+
209
+ ## Security
210
+
211
+ Do not open public issues with API keys, cookies, account passwords, proxy credentials, or target-site secrets. See [SECURITY.md](SECURITY.md).
@@ -0,0 +1,188 @@
1
+ # NexaLayer SDK
2
+
3
+ Managed network sessions for Playwright, Browser Use, AI agents, and automation scripts.
4
+
5
+ **Keep using proxies. Stop managing them.**
6
+ 中文:代理继续使用,代理管理交给 NexaLayer。
7
+
8
+ This repository contains the Python and Node/TypeScript SDK source. Release automation publishes signed release artifacts to PyPI and npm after registry ownership and secrets are configured.
9
+
10
+ ## Status
11
+
12
+ | Runtime | Source | Package registry status |
13
+ | --- | --- | --- |
14
+ | Python | `python/` | Package name `nexalayer`; release workflow configured |
15
+ | Node / TypeScript | `node/` | Package name `@nexalayer/sdk`; release workflow configured |
16
+
17
+ ## Installation
18
+
19
+ Python from repository root:
20
+
21
+ ```bash
22
+ python3 -m venv .venv
23
+ . .venv/bin/activate
24
+ pip install -e ".[dev]"
25
+ ```
26
+
27
+ Node from repository root:
28
+
29
+ ```bash
30
+ cd node
31
+ npm install
32
+ npm run build
33
+ ```
34
+
35
+ ## Authentication
36
+
37
+ ```bash
38
+ export NEXALAYER_BASE_URL=https://api.nexalayer.net/v1
39
+ export NEXALAYER_API_KEY=agk_your_key
40
+ ```
41
+
42
+ The SDK uses `X-API-Key` for Agent requests.
43
+
44
+ ## 60-second Python example
45
+
46
+ This example auto-selects a dynamic product, creates a session, reports telemetry, reads health, and terminates the session in cleanup.
47
+
48
+ ```python
49
+ import os
50
+ import time
51
+ import uuid
52
+
53
+ import requests
54
+ from nexalayer import NexaLayerClient
55
+
56
+ api_key = os.environ["NEXALAYER_API_KEY"]
57
+ client = NexaLayerClient(api_key=api_key)
58
+ session_id = None
59
+
60
+ def data(resp):
61
+ return resp.get("data", resp)
62
+
63
+ try:
64
+ products = data(client.get_products(type="dynamic"))
65
+ product = next(p for p in products.get("items", []) if p.get("product_no"))
66
+
67
+ session = client.create_session(
68
+ session_type="dynamic",
69
+ product_no=product["product_no"],
70
+ quantity=1,
71
+ protocol="socks5",
72
+ rotation_mode="on_demand",
73
+ idempotency_key=f"py-{uuid.uuid4()}",
74
+ )
75
+ session_id = session.session_id
76
+
77
+ for _ in range(60):
78
+ current = data(client.get_session(session_id))
79
+ if current.get("status") == "active":
80
+ proxy_url = current["proxy"]["full_url"]
81
+ break
82
+ time.sleep(2)
83
+ else:
84
+ raise TimeoutError("Session did not become active")
85
+
86
+ res = requests.get(
87
+ "https://httpbin.org/ip",
88
+ proxies={"http": proxy_url, "https": proxy_url},
89
+ timeout=30,
90
+ )
91
+ client.report_event(
92
+ session_id,
93
+ event_type="success" if res.ok else "http_error",
94
+ status_code=res.status_code,
95
+ target_host="httpbin.org",
96
+ )
97
+ print(client.get_session_health(session_id))
98
+ finally:
99
+ if session_id:
100
+ client.terminate_session(session_id)
101
+ ```
102
+
103
+ ## Dynamic Session
104
+
105
+ Use a dynamic session when your automation can rotate after errors such as timeout, 403, 429, or explicit blocks.
106
+
107
+ ```python
108
+ session = client.create_session(
109
+ session_type="dynamic",
110
+ product_no="selected_from_products_api",
111
+ quantity=1,
112
+ protocol="socks5",
113
+ rotation_mode="on_demand",
114
+ )
115
+ ```
116
+
117
+ ## Static Session
118
+
119
+ Use a static session when you need fixed network identity for a longer-lived task.
120
+
121
+ ```python
122
+ session = client.create_session(
123
+ session_type="static",
124
+ product_no="selected_from_products_api",
125
+ quantity=1,
126
+ duration=30,
127
+ protocol="socks5",
128
+ )
129
+ ```
130
+
131
+ ## Telemetry
132
+
133
+ ```python
134
+ client.report_event(
135
+ session_id,
136
+ event_type="timeout",
137
+ status_code=0,
138
+ target_host="example.com",
139
+ )
140
+ ```
141
+
142
+ ## Health
143
+
144
+ ```python
145
+ health = client.get_session_health(session_id)
146
+ ```
147
+
148
+ `timeout`, `captcha`, `block`, and repeated HTTP errors are not neutral noise; report them so NexaLayer can score the session and recommend rotate or pause.
149
+
150
+ ## Cleanup
151
+
152
+ Always terminate sessions you no longer need:
153
+
154
+ ```python
155
+ try:
156
+ ...
157
+ finally:
158
+ if session_id:
159
+ client.terminate_session(session_id)
160
+ ```
161
+
162
+ ## Playwright
163
+
164
+ Use the public Playwright examples:
165
+
166
+ - Chinese: https://github.com/NexaLayer/nexalayer-examples/tree/main/playwright/basic-session
167
+ - English: https://github.com/NexaLayer/nexalayer-examples/tree/main/playwright/basic-session
168
+
169
+ ## 中文文档
170
+
171
+ - 中文快速开始: https://docs.nexalayer.net/zh/quick-start
172
+ - Playwright 中文 Demo: https://docs.nexalayer.net/zh/examples
173
+ - API 文档: https://docs.nexalayer.net/api-reference/openapi
174
+
175
+ ## Error handling
176
+
177
+ - Check `backend_ready=true` before creating paid sessions after registration.
178
+ - Use `Idempotency-Key` for create, rotate, renew, and recharge operations.
179
+ - Do not log `proxy.full_url` in production without redaction because it can contain proxy credentials.
180
+ - Respect target website terms and applicable law.
181
+
182
+ ## Contributing
183
+
184
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
185
+
186
+ ## Security
187
+
188
+ Do not open public issues with API keys, cookies, account passwords, proxy credentials, or target-site secrets. See [SECURITY.md](SECURITY.md).
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "nexalayer"
7
+ version = "0.1.0"
8
+ description = "NexaLayer SDK — The Network Execution Layer for AI Agents"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ authors = [{ name = "NexaLayer" }]
13
+ keywords = ["proxy", "agent", "session", "nexalayer"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.9",
17
+ "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.11",
19
+ ]
20
+ dependencies = [
21
+ "requests>=2.28",
22
+ ]
23
+
24
+ [project.optional-dependencies]
25
+ dev = [
26
+ "pytest>=7",
27
+ "pytest-cov>=4",
28
+ ]
29
+
30
+ [project.urls]
31
+ Documentation = "https://docs.nexalayer.net"
32
+ Repository = "https://github.com/NexaLayer/nexalayer-sdk"
33
+ Issues = "https://github.com/NexaLayer/nexalayer-sdk/issues"
34
+
35
+ [tool.setuptools.packages.find]
36
+ where = ["python"]
37
+ exclude = ["tests*"]
38
+
39
+ [tool.pytest.ini_options]
40
+ testpaths = ["python/tests"]
41
+ pythonpath = ["python"]
42
+
43
+ [tool.black]
44
+ line-length = 88
45
+ target-version = ["py39", "py310", "py311"]
@@ -0,0 +1,28 @@
1
+ """NexaLayer SDK — The Network Execution Layer for AI Agents."""
2
+
3
+ from nexalayer.client import NexaLayerClient
4
+ from nexalayer.session import Session
5
+ from nexalayer.errors import (
6
+ NexaLayerError,
7
+ AuthError,
8
+ APIError,
9
+ SessionError,
10
+ )
11
+ from nexalayer.types import (
12
+ SessionConfig,
13
+ SessionCreateResponse,
14
+ TokenResponse,
15
+ )
16
+
17
+ __version__ = "0.1.0"
18
+ __all__ = [
19
+ "NexaLayerClient",
20
+ "Session",
21
+ "NexaLayerError",
22
+ "AuthError",
23
+ "APIError",
24
+ "SessionError",
25
+ "SessionConfig",
26
+ "SessionCreateResponse",
27
+ "TokenResponse",
28
+ ]
@@ -0,0 +1,240 @@
1
+ """NexaLayer API client — auth, account, billing, products, sessions, stats."""
2
+
3
+ from typing import Any, Optional, Union
4
+ from urllib.parse import urlencode
5
+
6
+ import requests
7
+
8
+ from nexalayer.errors import APIError, AuthError
9
+ from nexalayer.session import Session
10
+ from nexalayer.types import SessionConfig, SessionCreateResponse, TokenResponse
11
+
12
+ DEFAULT_BASE_URL = "https://api.nexalayer.net/v1"
13
+
14
+
15
+ class NexaLayerClient:
16
+ """HTTP client for NexaLayer API with auth header injection."""
17
+
18
+ def __init__(
19
+ self,
20
+ api_key: Optional[str] = None,
21
+ bearer_token: Optional[str] = None,
22
+ api_secret: Optional[str] = None,
23
+ base_url: str = DEFAULT_BASE_URL,
24
+ ):
25
+ self.base_url = base_url.rstrip("/")
26
+ self._api_key = api_key
27
+ self._bearer_token = bearer_token
28
+ self._api_secret = api_secret
29
+ self._session = requests.Session()
30
+ if bearer_token:
31
+ self._session.headers["Authorization"] = f"Bearer {bearer_token}"
32
+ elif api_key:
33
+ self._session.headers["X-API-Key"] = api_key
34
+
35
+ def _headers(self) -> dict:
36
+ h = {"Content-Type": "application/json", "Accept": "application/json"}
37
+ if self._bearer_token:
38
+ h["Authorization"] = f"Bearer {self._bearer_token}"
39
+ elif self._api_key:
40
+ h["X-API-Key"] = self._api_key
41
+ return h
42
+
43
+ def _request(
44
+ self,
45
+ method: str,
46
+ path: str,
47
+ json: Optional[dict] = None,
48
+ headers: Optional[dict] = None,
49
+ **kwargs: Any,
50
+ ) -> dict:
51
+ url = f"{self.base_url}{path}"
52
+ request_headers = self._headers()
53
+ if headers:
54
+ request_headers.update(headers)
55
+ resp = self._session.request(
56
+ method,
57
+ url,
58
+ json=json,
59
+ headers=request_headers,
60
+ timeout=kwargs.get("timeout", 30),
61
+ )
62
+ if resp.status_code >= 400:
63
+ raise APIError(
64
+ f"API error: {resp.status_code}",
65
+ status_code=resp.status_code,
66
+ body=resp.text,
67
+ )
68
+ return resp.json() if resp.content else {}
69
+
70
+ def get_token(self, api_key: Optional[str] = None, api_secret: Optional[str] = None) -> TokenResponse:
71
+ """POST /auth/token — obtain Bearer token from API Key/Secret."""
72
+ # TODO: call real POST /auth/token when backend is available
73
+ key = api_key or self._api_key
74
+ secret = api_secret or self._api_secret
75
+ if not key or not secret:
76
+ raise AuthError("api_key and api_secret required for get_token")
77
+ payload = {"api_key": key, "api_secret": secret}
78
+ data = self._request("POST", "/auth/token", json=payload)
79
+ # Placeholder; replace with real response parsing
80
+ token = data.get("data", {}).get("access_token") or "mock-token"
81
+ return TokenResponse(access_token=token)
82
+
83
+ def register(
84
+ self,
85
+ name: str,
86
+ contact_email: str,
87
+ referral_code: Optional[str] = None,
88
+ **kwargs: Any,
89
+ ) -> dict:
90
+ """POST /account/register."""
91
+ # TODO: implement real call
92
+ payload: dict = {"name": name, "contact_email": contact_email, **kwargs}
93
+ if referral_code is not None:
94
+ payload["referral_code"] = referral_code
95
+ return self._request("POST", "/account/register", json=payload)
96
+
97
+ def request_api_key(self, email: str, referral_code: Optional[str] = None) -> dict:
98
+ """POST /account/request-api-key; first-time emails receive trial credit."""
99
+ payload = {"email": email}
100
+ if referral_code:
101
+ payload["referral_code"] = referral_code
102
+ return self._request("POST", "/account/request-api-key", json=payload)
103
+
104
+ def get_balance(self) -> dict:
105
+ """GET /billing/balance."""
106
+ # TODO: implement real call
107
+ return self._request("GET", "/billing/balance")
108
+
109
+ def recharge(
110
+ self,
111
+ amount: float,
112
+ currency: str = "USD",
113
+ idempotency_key: Optional[str] = None,
114
+ **kwargs: Any,
115
+ ) -> dict:
116
+ """POST /billing/recharge."""
117
+ # TODO: implement real call
118
+ payload = {"amount": amount, "currency": currency, **kwargs}
119
+ headers = {"Idempotency-Key": idempotency_key} if idempotency_key else None
120
+ return self._request("POST", "/billing/recharge", json=payload, headers=headers)
121
+
122
+ def get_budget(self) -> dict:
123
+ """GET /account/budget."""
124
+ return self._request("GET", "/account/budget")
125
+
126
+ def set_budget(
127
+ self,
128
+ *,
129
+ idempotency_key: str,
130
+ key_limit_usd: Optional[float] = None,
131
+ daily_limit_usd: Optional[float] = None,
132
+ pause_on_exceed: bool = True,
133
+ alert_thresholds: Optional[list] = None,
134
+ paused: Optional[bool] = None,
135
+ ) -> dict:
136
+ """PUT /account/budget."""
137
+ if not idempotency_key:
138
+ raise ValueError("idempotency_key is required")
139
+ payload = {
140
+ "key_limit_usd": key_limit_usd,
141
+ "daily_limit_usd": daily_limit_usd,
142
+ "pause_on_exceed": pause_on_exceed,
143
+ "alert_thresholds": alert_thresholds or [50, 80, 100],
144
+ }
145
+ if paused is not None:
146
+ payload["paused"] = paused
147
+ return self._request(
148
+ "PUT",
149
+ "/account/budget",
150
+ json=payload,
151
+ headers={"Idempotency-Key": idempotency_key},
152
+ )
153
+
154
+ def get_usage_alerts(self) -> dict:
155
+ """GET /account/usage-alerts."""
156
+ return self._request("GET", "/account/usage-alerts")
157
+
158
+ def get_products(self, type: str = "all", country_code: Optional[str] = None) -> dict:
159
+ """GET /products."""
160
+ query = {"type": type}
161
+ if country_code:
162
+ query["country_code"] = country_code
163
+ return self._request("GET", f"/products?{urlencode(query)}")
164
+
165
+ def recommend_products(self, criteria: dict) -> dict:
166
+ """POST /products/recommend."""
167
+ # TODO: implement real call
168
+ return self._request("POST", "/products/recommend", json=criteria)
169
+
170
+ def create_session(
171
+ self,
172
+ type: Optional[str] = None,
173
+ session_type: Optional[str] = None,
174
+ product_no: Optional[str] = None,
175
+ config: Optional[Union[dict, SessionConfig]] = None,
176
+ idempotency_key: Optional[str] = None,
177
+ **kwargs: Any,
178
+ ) -> Session:
179
+ """POST /sessions — create a session and return a Session object."""
180
+ cfg = config.to_dict() if isinstance(config, SessionConfig) else (config or {})
181
+ resolved_type = session_type or type or cfg.pop("session_type", "dynamic")
182
+ resolved_product = product_no or cfg.pop("product_no", None)
183
+ if not resolved_product:
184
+ raise APIError("product_no is required")
185
+ payload = {
186
+ "session_type": resolved_type,
187
+ "product_no": resolved_product,
188
+ **cfg,
189
+ **kwargs,
190
+ }
191
+ headers = {"Idempotency-Key": idempotency_key} if idempotency_key else None
192
+ data = self._request("POST", "/sessions", json=payload, headers=headers)
193
+ inner = data.get("data", data)
194
+ session_id = inner.get("session_id")
195
+ if not session_id:
196
+ raise APIError(f"Session create response missing session_id: {data}")
197
+ status = inner.get("status") or "creating"
198
+ proxy_config = inner.get("proxy") or inner.get("proxy_config") or {}
199
+ resp = SessionCreateResponse(
200
+ session_id=session_id,
201
+ status=status,
202
+ proxy_config=proxy_config,
203
+ )
204
+ return Session(
205
+ client=self,
206
+ session_id=resp.session_id,
207
+ proxy_config=resp.proxy_config,
208
+ )
209
+
210
+ def get_session(self, session_id: str) -> dict:
211
+ """GET /sessions/{session_id}."""
212
+ # TODO: implement real call
213
+ return self._request("GET", f"/sessions/{session_id}")
214
+
215
+ def rotate_session(self, session_id: str, idempotency_key: Optional[str] = None) -> dict:
216
+ """POST /sessions/{session_id}/rotate."""
217
+ headers = {"Idempotency-Key": idempotency_key} if idempotency_key else None
218
+ return self._request("POST", f"/sessions/{session_id}/rotate", headers=headers)
219
+
220
+ def terminate_session(self, session_id: str) -> dict:
221
+ """DELETE /sessions/{session_id}."""
222
+ return self._request("DELETE", f"/sessions/{session_id}")
223
+
224
+ def report_event(self, session_id: str, **event: Any) -> dict:
225
+ """POST /sessions/{session_id}/report-event."""
226
+ return self._request("POST", f"/sessions/{session_id}/report-event", json=event)
227
+
228
+ def get_session_health(self, session_id: str) -> dict:
229
+ """GET /sessions/{session_id}/health."""
230
+ return self._request("GET", f"/sessions/{session_id}/health")
231
+
232
+ def get_session_usage(self, session_id: str) -> dict:
233
+ """GET /sessions/{session_id}/usage."""
234
+ # TODO: implement real call
235
+ return self._request("GET", f"/sessions/{session_id}/usage")
236
+
237
+ def get_stats_overview(self) -> dict:
238
+ """GET /stats/overview."""
239
+ # TODO: implement real call
240
+ return self._request("GET", "/stats/overview")
@@ -0,0 +1,30 @@
1
+ """NexaLayer SDK error classes."""
2
+
3
+ from typing import Optional
4
+
5
+
6
+ class NexaLayerError(Exception):
7
+ """Base exception for NexaLayer SDK."""
8
+
9
+ def __init__(
10
+ self,
11
+ message: str,
12
+ status_code: Optional[int] = None,
13
+ body: Optional[str] = None,
14
+ ):
15
+ super().__init__(message)
16
+ self.message = message
17
+ self.status_code = status_code
18
+ self.body = body
19
+
20
+
21
+ class AuthError(NexaLayerError):
22
+ """Authentication or authorization failed."""
23
+
24
+
25
+ class APIError(NexaLayerError):
26
+ """API returned an error (4xx/5xx)."""
27
+
28
+
29
+ class SessionError(NexaLayerError):
30
+ """Session operation failed (create, rotate, get, etc.)."""
@@ -0,0 +1,46 @@
1
+ """Session abstraction for NexaLayer managed sessions."""
2
+
3
+ from typing import TYPE_CHECKING, Any, Optional
4
+
5
+ import requests
6
+
7
+ from nexalayer.errors import SessionError
8
+
9
+ if TYPE_CHECKING:
10
+ from nexalayer.client import NexaLayerClient
11
+
12
+
13
+ class Session:
14
+ """Represents a NexaLayer proxy session; use get/post to send traffic via proxy."""
15
+
16
+ def __init__(
17
+ self,
18
+ client: "NexaLayerClient",
19
+ session_id: str,
20
+ proxy_config: Optional[dict] = None,
21
+ ):
22
+ self.client = client
23
+ self.session_id = session_id
24
+ self.proxy_config = proxy_config or {}
25
+ self._session = requests.Session()
26
+ # Transport helpers are intentionally minimal. Production integrations
27
+ # should use proxy.full_url or host/port credentials from GET /sessions.
28
+ # self._session.proxies = {"http": ..., "https": ...}
29
+
30
+ def get(self, url: str, **kwargs: Any) -> requests.Response:
31
+ """Perform GET with the local requests session."""
32
+ # TODO: use proxy_config to route via proxy when available
33
+ return self._session.get(url, timeout=kwargs.get("timeout", 30))
34
+
35
+ def post(self, url: str, data: Any = None, json: Any = None, **kwargs: Any) -> requests.Response:
36
+ """Perform POST with the local requests session."""
37
+ # TODO: use proxy_config to route via proxy when available
38
+ return self._session.post(url, data=data, json=json, timeout=kwargs.get("timeout", 30))
39
+
40
+ def rotate(self) -> dict:
41
+ """POST /sessions/{session_id}/rotate — rotate proxy for this session."""
42
+ return self.client.rotate_session(self.session_id)
43
+
44
+ def usage(self) -> dict:
45
+ """GET /sessions/{session_id}/usage."""
46
+ return self.client.get_session_usage(self.session_id)
@@ -0,0 +1,59 @@
1
+ """Lightweight request/response types for NexaLayer API."""
2
+
3
+ from typing import Any, Optional
4
+
5
+
6
+ class SessionConfig:
7
+ """Configuration for creating a session (dynamic or static)."""
8
+
9
+ def __init__(
10
+ self,
11
+ product_no: Optional[str] = None,
12
+ traffic_gb: Optional[float] = None,
13
+ protocol: Optional[str] = None,
14
+ country: Optional[str] = None,
15
+ **kwargs: Any,
16
+ ):
17
+ self.product_no = product_no
18
+ self.traffic_gb = traffic_gb
19
+ self.protocol = protocol
20
+ self.country = country
21
+ self.extra = kwargs
22
+
23
+ def to_dict(self) -> dict:
24
+ out = {}
25
+ if self.product_no is not None:
26
+ out["product_no"] = self.product_no
27
+ if self.traffic_gb is not None:
28
+ out["traffic_gb"] = self.traffic_gb
29
+ if self.protocol is not None:
30
+ out["protocol"] = self.protocol
31
+ if self.country is not None:
32
+ out["country"] = self.country
33
+ out.update(self.extra)
34
+ return out
35
+
36
+
37
+ class TokenResponse:
38
+ """Response from POST /auth/token."""
39
+
40
+ def __init__(self, access_token: str, token_type: str = "Bearer", **kwargs: Any):
41
+ self.access_token = access_token
42
+ self.token_type = token_type
43
+ self.extra = kwargs
44
+
45
+
46
+ class SessionCreateResponse:
47
+ """Response from POST /sessions."""
48
+
49
+ def __init__(
50
+ self,
51
+ session_id: str,
52
+ status: str,
53
+ proxy_config: Optional[dict] = None,
54
+ **kwargs: Any,
55
+ ):
56
+ self.session_id = session_id
57
+ self.status = status
58
+ self.proxy_config = proxy_config or {}
59
+ self.extra = kwargs
@@ -0,0 +1,211 @@
1
+ Metadata-Version: 2.4
2
+ Name: nexalayer
3
+ Version: 0.1.0
4
+ Summary: NexaLayer SDK — The Network Execution Layer for AI Agents
5
+ Author: NexaLayer
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://docs.nexalayer.net
8
+ Project-URL: Repository, https://github.com/NexaLayer/nexalayer-sdk
9
+ Project-URL: Issues, https://github.com/NexaLayer/nexalayer-sdk/issues
10
+ Keywords: proxy,agent,session,nexalayer
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: requests>=2.28
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest>=7; extra == "dev"
21
+ Requires-Dist: pytest-cov>=4; extra == "dev"
22
+ Dynamic: license-file
23
+
24
+ # NexaLayer SDK
25
+
26
+ Managed network sessions for Playwright, Browser Use, AI agents, and automation scripts.
27
+
28
+ **Keep using proxies. Stop managing them.**
29
+ 中文:代理继续使用,代理管理交给 NexaLayer。
30
+
31
+ This repository contains the Python and Node/TypeScript SDK source. Release automation publishes signed release artifacts to PyPI and npm after registry ownership and secrets are configured.
32
+
33
+ ## Status
34
+
35
+ | Runtime | Source | Package registry status |
36
+ | --- | --- | --- |
37
+ | Python | `python/` | Package name `nexalayer`; release workflow configured |
38
+ | Node / TypeScript | `node/` | Package name `@nexalayer/sdk`; release workflow configured |
39
+
40
+ ## Installation
41
+
42
+ Python from repository root:
43
+
44
+ ```bash
45
+ python3 -m venv .venv
46
+ . .venv/bin/activate
47
+ pip install -e ".[dev]"
48
+ ```
49
+
50
+ Node from repository root:
51
+
52
+ ```bash
53
+ cd node
54
+ npm install
55
+ npm run build
56
+ ```
57
+
58
+ ## Authentication
59
+
60
+ ```bash
61
+ export NEXALAYER_BASE_URL=https://api.nexalayer.net/v1
62
+ export NEXALAYER_API_KEY=agk_your_key
63
+ ```
64
+
65
+ The SDK uses `X-API-Key` for Agent requests.
66
+
67
+ ## 60-second Python example
68
+
69
+ This example auto-selects a dynamic product, creates a session, reports telemetry, reads health, and terminates the session in cleanup.
70
+
71
+ ```python
72
+ import os
73
+ import time
74
+ import uuid
75
+
76
+ import requests
77
+ from nexalayer import NexaLayerClient
78
+
79
+ api_key = os.environ["NEXALAYER_API_KEY"]
80
+ client = NexaLayerClient(api_key=api_key)
81
+ session_id = None
82
+
83
+ def data(resp):
84
+ return resp.get("data", resp)
85
+
86
+ try:
87
+ products = data(client.get_products(type="dynamic"))
88
+ product = next(p for p in products.get("items", []) if p.get("product_no"))
89
+
90
+ session = client.create_session(
91
+ session_type="dynamic",
92
+ product_no=product["product_no"],
93
+ quantity=1,
94
+ protocol="socks5",
95
+ rotation_mode="on_demand",
96
+ idempotency_key=f"py-{uuid.uuid4()}",
97
+ )
98
+ session_id = session.session_id
99
+
100
+ for _ in range(60):
101
+ current = data(client.get_session(session_id))
102
+ if current.get("status") == "active":
103
+ proxy_url = current["proxy"]["full_url"]
104
+ break
105
+ time.sleep(2)
106
+ else:
107
+ raise TimeoutError("Session did not become active")
108
+
109
+ res = requests.get(
110
+ "https://httpbin.org/ip",
111
+ proxies={"http": proxy_url, "https": proxy_url},
112
+ timeout=30,
113
+ )
114
+ client.report_event(
115
+ session_id,
116
+ event_type="success" if res.ok else "http_error",
117
+ status_code=res.status_code,
118
+ target_host="httpbin.org",
119
+ )
120
+ print(client.get_session_health(session_id))
121
+ finally:
122
+ if session_id:
123
+ client.terminate_session(session_id)
124
+ ```
125
+
126
+ ## Dynamic Session
127
+
128
+ Use a dynamic session when your automation can rotate after errors such as timeout, 403, 429, or explicit blocks.
129
+
130
+ ```python
131
+ session = client.create_session(
132
+ session_type="dynamic",
133
+ product_no="selected_from_products_api",
134
+ quantity=1,
135
+ protocol="socks5",
136
+ rotation_mode="on_demand",
137
+ )
138
+ ```
139
+
140
+ ## Static Session
141
+
142
+ Use a static session when you need fixed network identity for a longer-lived task.
143
+
144
+ ```python
145
+ session = client.create_session(
146
+ session_type="static",
147
+ product_no="selected_from_products_api",
148
+ quantity=1,
149
+ duration=30,
150
+ protocol="socks5",
151
+ )
152
+ ```
153
+
154
+ ## Telemetry
155
+
156
+ ```python
157
+ client.report_event(
158
+ session_id,
159
+ event_type="timeout",
160
+ status_code=0,
161
+ target_host="example.com",
162
+ )
163
+ ```
164
+
165
+ ## Health
166
+
167
+ ```python
168
+ health = client.get_session_health(session_id)
169
+ ```
170
+
171
+ `timeout`, `captcha`, `block`, and repeated HTTP errors are not neutral noise; report them so NexaLayer can score the session and recommend rotate or pause.
172
+
173
+ ## Cleanup
174
+
175
+ Always terminate sessions you no longer need:
176
+
177
+ ```python
178
+ try:
179
+ ...
180
+ finally:
181
+ if session_id:
182
+ client.terminate_session(session_id)
183
+ ```
184
+
185
+ ## Playwright
186
+
187
+ Use the public Playwright examples:
188
+
189
+ - Chinese: https://github.com/NexaLayer/nexalayer-examples/tree/main/playwright/basic-session
190
+ - English: https://github.com/NexaLayer/nexalayer-examples/tree/main/playwright/basic-session
191
+
192
+ ## 中文文档
193
+
194
+ - 中文快速开始: https://docs.nexalayer.net/zh/quick-start
195
+ - Playwright 中文 Demo: https://docs.nexalayer.net/zh/examples
196
+ - API 文档: https://docs.nexalayer.net/api-reference/openapi
197
+
198
+ ## Error handling
199
+
200
+ - Check `backend_ready=true` before creating paid sessions after registration.
201
+ - Use `Idempotency-Key` for create, rotate, renew, and recharge operations.
202
+ - Do not log `proxy.full_url` in production without redaction because it can contain proxy credentials.
203
+ - Respect target website terms and applicable law.
204
+
205
+ ## Contributing
206
+
207
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
208
+
209
+ ## Security
210
+
211
+ Do not open public issues with API keys, cookies, account passwords, proxy credentials, or target-site secrets. See [SECURITY.md](SECURITY.md).
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ python/nexalayer/__init__.py
5
+ python/nexalayer/client.py
6
+ python/nexalayer/errors.py
7
+ python/nexalayer/session.py
8
+ python/nexalayer/types.py
9
+ python/nexalayer.egg-info/PKG-INFO
10
+ python/nexalayer.egg-info/SOURCES.txt
11
+ python/nexalayer.egg-info/dependency_links.txt
12
+ python/nexalayer.egg-info/requires.txt
13
+ python/nexalayer.egg-info/top_level.txt
@@ -0,0 +1,5 @@
1
+ requests>=2.28
2
+
3
+ [dev]
4
+ pytest>=7
5
+ pytest-cov>=4
@@ -0,0 +1 @@
1
+ nexalayer
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+