newapi-python 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.
- newapi_python-0.1.0/.gitignore +11 -0
- newapi_python-0.1.0/LICENSE +21 -0
- newapi_python-0.1.0/PKG-INFO +267 -0
- newapi_python-0.1.0/README.md +237 -0
- newapi_python-0.1.0/pyproject.toml +62 -0
- newapi_python-0.1.0/src/newapi/__init__.py +75 -0
- newapi_python-0.1.0/src/newapi/_client.py +620 -0
- newapi_python-0.1.0/src/newapi/_crypto.py +100 -0
- newapi_python-0.1.0/src/newapi/_exceptions.py +76 -0
- newapi_python-0.1.0/src/newapi/_models.py +244 -0
- newapi_python-0.1.0/src/newapi/_resources.py +775 -0
- newapi_python-0.1.0/src/newapi/py.typed +0 -0
- newapi_python-0.1.0/tests/test_client.py +459 -0
- newapi_python-0.1.0/tests/test_resources.py +456 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Eight Labs
|
|
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,267 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: newapi-python
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for the user-facing dashboard API of new-api instances
|
|
5
|
+
Author: Eight Labs
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: ai-gateway,api,new-api,one-api,sdk
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: curl-cffi<1,>=0.10
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
22
|
+
Requires-Dist: cryptography>=43; extra == 'dev'
|
|
23
|
+
Requires-Dist: httpx<1,>=0.27; extra == 'dev'
|
|
24
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest-cov>=5; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff>=0.7; extra == 'dev'
|
|
28
|
+
Requires-Dist: twine>=5; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# new-api
|
|
32
|
+
|
|
33
|
+
`newapi` is a Python client for the shared user-facing dashboard API exposed by [new-api](https://github.com/QuantumNous/new-api) instances. One client object represents one user's in-memory dashboard session. Requests use `curl_cffi` with Chrome browser impersonation by default.
|
|
34
|
+
|
|
35
|
+
The library targets operations present on standard new-api deployments: account profile, quota balance, gateway tokens, usage logs and statistics, dashboard data, usable groups, optional top-up and online payment, redemption codes, subscriptions, check-in, and login-session management.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install newapi-python
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Python 3.10 or newer is required.
|
|
44
|
+
|
|
45
|
+
## Authenticate with an existing session
|
|
46
|
+
|
|
47
|
+
The dashboard's access token is different from an `sk-...` gateway token. Browser deployments normally hold the session access token in memory after login or refresh, and keep the rotating refresh credential in the HttpOnly `new_api_refresh` cookie scoped to `/api/user/auth`.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import os
|
|
51
|
+
|
|
52
|
+
from newapi import NewAPI
|
|
53
|
+
|
|
54
|
+
client = NewAPI(
|
|
55
|
+
"https://newapi.example.com",
|
|
56
|
+
access_token=os.environ["NEWAPI_ACCESS_TOKEN"],
|
|
57
|
+
refresh_token=os.environ.get("NEWAPI_REFRESH_TOKEN"),
|
|
58
|
+
)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Pass either the instance origin or its full `/api` URL. Tokens are retained only in memory. If a refresh token is supplied, the client sends it as the `new_api_refresh` cookie, rotates the pair after an authenticated `401`, and refreshes proactively when `expires_at` is known. Users can also generate a long-lived system access token from the dashboard and pass it as `access_token` alone; such tokens cannot refresh browser sessions.
|
|
62
|
+
|
|
63
|
+
The default browser fingerprint is Chrome. Choose another `curl_cffi` fingerprint or configure proxies by supplying your own `curl_cffi.requests.Session`:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from curl_cffi import requests
|
|
67
|
+
|
|
68
|
+
session = requests.Session(impersonate="safari")
|
|
69
|
+
client = NewAPI("https://newapi.example.com", session=session)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Log in with username and password
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from newapi import NewAPI
|
|
76
|
+
|
|
77
|
+
with NewAPI("https://newapi.example.com") as client:
|
|
78
|
+
user = client.login("root", "password")
|
|
79
|
+
print(user.username, user.quota)
|
|
80
|
+
print(client.is_authenticated)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
When the instance enables login password encryption, the client fetches the RSA public key from `/api/user/login/encryption-key` and submits an RSA-OAEP/SHA-256 ciphertext, matching the browser. Pass `encrypt_password=False` to send the plaintext password instead. An instance with Cloudflare Turnstile enabled requires the corresponding proof:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
client.login("root", "password", turnstile_token="captcha-proof")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
For a TOTP-enabled account, `login()` raises `TwoFactorRequired` and retains the flow token in memory:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from newapi import NewAPI, TwoFactorRequired
|
|
93
|
+
|
|
94
|
+
client = NewAPI("https://newapi.example.com")
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
client.login("root", "password")
|
|
98
|
+
except TwoFactorRequired:
|
|
99
|
+
client.complete_2fa("123456")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Common operations
|
|
103
|
+
|
|
104
|
+
Resources are callable for their common list operation and also expose explicit methods.
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
balance = client.balance()
|
|
108
|
+
print(balance.quota, balance.amount)
|
|
109
|
+
|
|
110
|
+
status = client.status()
|
|
111
|
+
groups = client.groups()
|
|
112
|
+
print(groups["vip"].ratio, groups["vip"].desc)
|
|
113
|
+
|
|
114
|
+
models = client.account.models()
|
|
115
|
+
print(client.account.aff_code())
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`balance()` reports raw quota integers plus `amount`, `used_amount`, and `aff_amount` as `Decimal` values converted with the instance's `quota_per_unit` (default `500000`).
|
|
119
|
+
|
|
120
|
+
## Gateway tokens
|
|
121
|
+
|
|
122
|
+
`tokens` and `keys` refer to the same resource. Instances return masked key values in list responses; call `reveal()` to fetch the full `sk-...` value.
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
first_page = client.tokens(page_size=50)
|
|
126
|
+
for token in first_page:
|
|
127
|
+
print(token.id, token.name, token.status, token.remain_quota)
|
|
128
|
+
|
|
129
|
+
all_tokens = client.tokens.all()
|
|
130
|
+
|
|
131
|
+
client.tokens.create("automation", group="default")
|
|
132
|
+
token, key = client.tokens.create_and_reveal("automation", group="default")
|
|
133
|
+
client.tokens.update(token.id, name="nightly automation")
|
|
134
|
+
client.tokens.disable(token.id)
|
|
135
|
+
client.tokens.enable(token.id)
|
|
136
|
+
client.tokens.delete(token.id)
|
|
137
|
+
|
|
138
|
+
print(client.tokens.reveal(token.id))
|
|
139
|
+
print(client.tokens.reveal_batch([1, 2, 3]))
|
|
140
|
+
print(client.tokens.auto_groups())
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Token creation does not echo the new record from the instance, so `create()` returns `None`; `create_and_reveal()` creates the token, finds it in the list, and returns the record together with its full key. `update()` reads the current token first and resubmits preserved values for any field you leave out, because the instance replaces the whole record on update. `expired_time` accepts a Unix timestamp, a `datetime`, or `-1` for no expiry. `allow_ips` accepts a comma-separated string or a sequence of strings.
|
|
144
|
+
|
|
145
|
+
## Usage history
|
|
146
|
+
|
|
147
|
+
`history`, `usage`, and `logs` refer to the same resource.
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from datetime import datetime, timedelta, timezone
|
|
151
|
+
|
|
152
|
+
end = datetime.now(timezone.utc)
|
|
153
|
+
start = end - timedelta(days=7)
|
|
154
|
+
|
|
155
|
+
page = client.history(
|
|
156
|
+
log_type="consume",
|
|
157
|
+
start_timestamp=start,
|
|
158
|
+
end_timestamp=end,
|
|
159
|
+
page_size=100,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
for record in page:
|
|
163
|
+
print(record.created_at, record.model_name, record.quota, record.prompt_tokens)
|
|
164
|
+
|
|
165
|
+
for record in client.history.iter(log_type="consume", page_size=100):
|
|
166
|
+
process(record)
|
|
167
|
+
|
|
168
|
+
stats = client.logs.stat(log_type="consume", start_timestamp=start, end_timestamp=end)
|
|
169
|
+
print(stats.quota, stats.rpm, stats.tpm)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`log_type` accepts an integer or one of `topup`, `consume`, `manage`, `system`, `error`, `refund`, and `login`. Timestamps accept `datetime` objects or Unix seconds.
|
|
173
|
+
|
|
174
|
+
## Dashboard data
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
rows = client.dashboard.quota_data(start_timestamp=start, end_timestamp=end)
|
|
178
|
+
flow = client.dashboard.flow_data(start_timestamp=start, end_timestamp=end)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Both endpoints limit the time span to one month; `flow_data` requires explicit positive bounds.
|
|
182
|
+
|
|
183
|
+
## Top-up, redemption, and payment
|
|
184
|
+
|
|
185
|
+
Online payment is optional and must be enabled and configured by the instance administrator. Inspect the top-up configuration before offering a recharge:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
info = client.topup.info()
|
|
189
|
+
|
|
190
|
+
if info.enable_stripe_topup:
|
|
191
|
+
link = client.payment.stripe_pay(10)
|
|
192
|
+
print(link)
|
|
193
|
+
|
|
194
|
+
if info.enable_online_topup:
|
|
195
|
+
amount = client.payment.epay_amount(10)
|
|
196
|
+
checkout = client.payment.epay_pay(10, "alipay")
|
|
197
|
+
print(checkout.url, checkout.params)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Creating an order does not credit the balance; the configured provider must confirm payment before the instance completes the top-up. Track the resulting orders and redeem codes with:
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
orders = client.topup.orders()
|
|
204
|
+
result = client.topup.redeem("REDEMPTION-CODE")
|
|
205
|
+
print(result.quota)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Subscriptions
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
plans = client.subscriptions.plans()
|
|
212
|
+
overview = client.subscriptions.self()
|
|
213
|
+
client.subscriptions.set_preference("balance_first")
|
|
214
|
+
client.subscriptions.purchase_with_balance(plans[0].id)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Check-in
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
status = client.account.checkin_status()
|
|
221
|
+
if status.enabled:
|
|
222
|
+
result = client.account.checkin()
|
|
223
|
+
print(result.quota_awarded, result.checkin_date)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Login sessions
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
for entry in client.account.sessions():
|
|
230
|
+
print(entry.sid, entry.login_method, entry.current)
|
|
231
|
+
|
|
232
|
+
client.account.revoke_session("sid-from-another-device")
|
|
233
|
+
client.account.revoke_other_sessions()
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
These endpoints require a browser login session; long-lived system access tokens are rejected.
|
|
237
|
+
|
|
238
|
+
## Fork-specific endpoints
|
|
239
|
+
|
|
240
|
+
`request()` provides the same authentication, envelope handling, refresh behavior, and error mapping for relative endpoints that are not part of the stable resource API.
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
result = client.request("GET", "user/self")
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Absolute URLs and parent-path traversal are rejected so a session token cannot be redirected outside the configured API root.
|
|
247
|
+
|
|
248
|
+
## Errors
|
|
249
|
+
|
|
250
|
+
HTTP failures and new-api envelope failures use typed exceptions:
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
from newapi import APIError, AuthenticationError, NewAPIError, RateLimitError
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
client.tokens.create("automation")
|
|
257
|
+
except RateLimitError as error:
|
|
258
|
+
print(error.retry_after)
|
|
259
|
+
except AuthenticationError:
|
|
260
|
+
client.login("root", "password")
|
|
261
|
+
except NewAPIError as error:
|
|
262
|
+
print(error)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Most new-api business errors arrive as HTTP `200` with `success: false`; they raise `APIError` with the instance's message. Middleware failures use real HTTP statuses and map to `AuthenticationError`, `PermissionDeniedError`, `RateLimitError`, and friends. Object representations redact fields that commonly contain credentials.
|
|
266
|
+
|
|
267
|
+
Remote plaintext HTTP is rejected by default because it exposes login credentials and tokens. Localhost HTTP is allowed for development; other HTTP instances require `allow_insecure=True`.
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# new-api
|
|
2
|
+
|
|
3
|
+
`newapi` is a Python client for the shared user-facing dashboard API exposed by [new-api](https://github.com/QuantumNous/new-api) instances. One client object represents one user's in-memory dashboard session. Requests use `curl_cffi` with Chrome browser impersonation by default.
|
|
4
|
+
|
|
5
|
+
The library targets operations present on standard new-api deployments: account profile, quota balance, gateway tokens, usage logs and statistics, dashboard data, usable groups, optional top-up and online payment, redemption codes, subscriptions, check-in, and login-session management.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install newapi-python
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Python 3.10 or newer is required.
|
|
14
|
+
|
|
15
|
+
## Authenticate with an existing session
|
|
16
|
+
|
|
17
|
+
The dashboard's access token is different from an `sk-...` gateway token. Browser deployments normally hold the session access token in memory after login or refresh, and keep the rotating refresh credential in the HttpOnly `new_api_refresh` cookie scoped to `/api/user/auth`.
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import os
|
|
21
|
+
|
|
22
|
+
from newapi import NewAPI
|
|
23
|
+
|
|
24
|
+
client = NewAPI(
|
|
25
|
+
"https://newapi.example.com",
|
|
26
|
+
access_token=os.environ["NEWAPI_ACCESS_TOKEN"],
|
|
27
|
+
refresh_token=os.environ.get("NEWAPI_REFRESH_TOKEN"),
|
|
28
|
+
)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Pass either the instance origin or its full `/api` URL. Tokens are retained only in memory. If a refresh token is supplied, the client sends it as the `new_api_refresh` cookie, rotates the pair after an authenticated `401`, and refreshes proactively when `expires_at` is known. Users can also generate a long-lived system access token from the dashboard and pass it as `access_token` alone; such tokens cannot refresh browser sessions.
|
|
32
|
+
|
|
33
|
+
The default browser fingerprint is Chrome. Choose another `curl_cffi` fingerprint or configure proxies by supplying your own `curl_cffi.requests.Session`:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from curl_cffi import requests
|
|
37
|
+
|
|
38
|
+
session = requests.Session(impersonate="safari")
|
|
39
|
+
client = NewAPI("https://newapi.example.com", session=session)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Log in with username and password
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from newapi import NewAPI
|
|
46
|
+
|
|
47
|
+
with NewAPI("https://newapi.example.com") as client:
|
|
48
|
+
user = client.login("root", "password")
|
|
49
|
+
print(user.username, user.quota)
|
|
50
|
+
print(client.is_authenticated)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
When the instance enables login password encryption, the client fetches the RSA public key from `/api/user/login/encryption-key` and submits an RSA-OAEP/SHA-256 ciphertext, matching the browser. Pass `encrypt_password=False` to send the plaintext password instead. An instance with Cloudflare Turnstile enabled requires the corresponding proof:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
client.login("root", "password", turnstile_token="captcha-proof")
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For a TOTP-enabled account, `login()` raises `TwoFactorRequired` and retains the flow token in memory:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from newapi import NewAPI, TwoFactorRequired
|
|
63
|
+
|
|
64
|
+
client = NewAPI("https://newapi.example.com")
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
client.login("root", "password")
|
|
68
|
+
except TwoFactorRequired:
|
|
69
|
+
client.complete_2fa("123456")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Common operations
|
|
73
|
+
|
|
74
|
+
Resources are callable for their common list operation and also expose explicit methods.
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
balance = client.balance()
|
|
78
|
+
print(balance.quota, balance.amount)
|
|
79
|
+
|
|
80
|
+
status = client.status()
|
|
81
|
+
groups = client.groups()
|
|
82
|
+
print(groups["vip"].ratio, groups["vip"].desc)
|
|
83
|
+
|
|
84
|
+
models = client.account.models()
|
|
85
|
+
print(client.account.aff_code())
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`balance()` reports raw quota integers plus `amount`, `used_amount`, and `aff_amount` as `Decimal` values converted with the instance's `quota_per_unit` (default `500000`).
|
|
89
|
+
|
|
90
|
+
## Gateway tokens
|
|
91
|
+
|
|
92
|
+
`tokens` and `keys` refer to the same resource. Instances return masked key values in list responses; call `reveal()` to fetch the full `sk-...` value.
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
first_page = client.tokens(page_size=50)
|
|
96
|
+
for token in first_page:
|
|
97
|
+
print(token.id, token.name, token.status, token.remain_quota)
|
|
98
|
+
|
|
99
|
+
all_tokens = client.tokens.all()
|
|
100
|
+
|
|
101
|
+
client.tokens.create("automation", group="default")
|
|
102
|
+
token, key = client.tokens.create_and_reveal("automation", group="default")
|
|
103
|
+
client.tokens.update(token.id, name="nightly automation")
|
|
104
|
+
client.tokens.disable(token.id)
|
|
105
|
+
client.tokens.enable(token.id)
|
|
106
|
+
client.tokens.delete(token.id)
|
|
107
|
+
|
|
108
|
+
print(client.tokens.reveal(token.id))
|
|
109
|
+
print(client.tokens.reveal_batch([1, 2, 3]))
|
|
110
|
+
print(client.tokens.auto_groups())
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Token creation does not echo the new record from the instance, so `create()` returns `None`; `create_and_reveal()` creates the token, finds it in the list, and returns the record together with its full key. `update()` reads the current token first and resubmits preserved values for any field you leave out, because the instance replaces the whole record on update. `expired_time` accepts a Unix timestamp, a `datetime`, or `-1` for no expiry. `allow_ips` accepts a comma-separated string or a sequence of strings.
|
|
114
|
+
|
|
115
|
+
## Usage history
|
|
116
|
+
|
|
117
|
+
`history`, `usage`, and `logs` refer to the same resource.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from datetime import datetime, timedelta, timezone
|
|
121
|
+
|
|
122
|
+
end = datetime.now(timezone.utc)
|
|
123
|
+
start = end - timedelta(days=7)
|
|
124
|
+
|
|
125
|
+
page = client.history(
|
|
126
|
+
log_type="consume",
|
|
127
|
+
start_timestamp=start,
|
|
128
|
+
end_timestamp=end,
|
|
129
|
+
page_size=100,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
for record in page:
|
|
133
|
+
print(record.created_at, record.model_name, record.quota, record.prompt_tokens)
|
|
134
|
+
|
|
135
|
+
for record in client.history.iter(log_type="consume", page_size=100):
|
|
136
|
+
process(record)
|
|
137
|
+
|
|
138
|
+
stats = client.logs.stat(log_type="consume", start_timestamp=start, end_timestamp=end)
|
|
139
|
+
print(stats.quota, stats.rpm, stats.tpm)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`log_type` accepts an integer or one of `topup`, `consume`, `manage`, `system`, `error`, `refund`, and `login`. Timestamps accept `datetime` objects or Unix seconds.
|
|
143
|
+
|
|
144
|
+
## Dashboard data
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
rows = client.dashboard.quota_data(start_timestamp=start, end_timestamp=end)
|
|
148
|
+
flow = client.dashboard.flow_data(start_timestamp=start, end_timestamp=end)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Both endpoints limit the time span to one month; `flow_data` requires explicit positive bounds.
|
|
152
|
+
|
|
153
|
+
## Top-up, redemption, and payment
|
|
154
|
+
|
|
155
|
+
Online payment is optional and must be enabled and configured by the instance administrator. Inspect the top-up configuration before offering a recharge:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
info = client.topup.info()
|
|
159
|
+
|
|
160
|
+
if info.enable_stripe_topup:
|
|
161
|
+
link = client.payment.stripe_pay(10)
|
|
162
|
+
print(link)
|
|
163
|
+
|
|
164
|
+
if info.enable_online_topup:
|
|
165
|
+
amount = client.payment.epay_amount(10)
|
|
166
|
+
checkout = client.payment.epay_pay(10, "alipay")
|
|
167
|
+
print(checkout.url, checkout.params)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Creating an order does not credit the balance; the configured provider must confirm payment before the instance completes the top-up. Track the resulting orders and redeem codes with:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
orders = client.topup.orders()
|
|
174
|
+
result = client.topup.redeem("REDEMPTION-CODE")
|
|
175
|
+
print(result.quota)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Subscriptions
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
plans = client.subscriptions.plans()
|
|
182
|
+
overview = client.subscriptions.self()
|
|
183
|
+
client.subscriptions.set_preference("balance_first")
|
|
184
|
+
client.subscriptions.purchase_with_balance(plans[0].id)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Check-in
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
status = client.account.checkin_status()
|
|
191
|
+
if status.enabled:
|
|
192
|
+
result = client.account.checkin()
|
|
193
|
+
print(result.quota_awarded, result.checkin_date)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Login sessions
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
for entry in client.account.sessions():
|
|
200
|
+
print(entry.sid, entry.login_method, entry.current)
|
|
201
|
+
|
|
202
|
+
client.account.revoke_session("sid-from-another-device")
|
|
203
|
+
client.account.revoke_other_sessions()
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
These endpoints require a browser login session; long-lived system access tokens are rejected.
|
|
207
|
+
|
|
208
|
+
## Fork-specific endpoints
|
|
209
|
+
|
|
210
|
+
`request()` provides the same authentication, envelope handling, refresh behavior, and error mapping for relative endpoints that are not part of the stable resource API.
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
result = client.request("GET", "user/self")
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Absolute URLs and parent-path traversal are rejected so a session token cannot be redirected outside the configured API root.
|
|
217
|
+
|
|
218
|
+
## Errors
|
|
219
|
+
|
|
220
|
+
HTTP failures and new-api envelope failures use typed exceptions:
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from newapi import APIError, AuthenticationError, NewAPIError, RateLimitError
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
client.tokens.create("automation")
|
|
227
|
+
except RateLimitError as error:
|
|
228
|
+
print(error.retry_after)
|
|
229
|
+
except AuthenticationError:
|
|
230
|
+
client.login("root", "password")
|
|
231
|
+
except NewAPIError as error:
|
|
232
|
+
print(error)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Most new-api business errors arrive as HTTP `200` with `success: false`; they raise `APIError` with the instance's message. Middleware failures use real HTTP statuses and map to `AuthenticationError`, `PermissionDeniedError`, `RateLimitError`, and friends. Object representations redact fields that commonly contain credentials.
|
|
236
|
+
|
|
237
|
+
Remote plaintext HTTP is rejected by default because it exposes login credentials and tokens. Localhost HTTP is allowed for development; other HTTP instances require `allow_insecure=True`.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.26"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "newapi-python"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python client for the user-facing dashboard API of new-api instances"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Eight Labs" }
|
|
15
|
+
]
|
|
16
|
+
keywords = ["new-api", "one-api", "api", "sdk", "ai-gateway"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 3 - Alpha",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Typing :: Typed"
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"curl-cffi>=0.10,<1"
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"build>=1.2",
|
|
35
|
+
"cryptography>=43",
|
|
36
|
+
"httpx>=0.27,<1",
|
|
37
|
+
"mypy>=1.11",
|
|
38
|
+
"pytest>=8",
|
|
39
|
+
"pytest-cov>=5",
|
|
40
|
+
"ruff>=0.7",
|
|
41
|
+
"twine>=5"
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.wheel]
|
|
45
|
+
packages = ["src/newapi"]
|
|
46
|
+
|
|
47
|
+
[tool.pytest.ini_options]
|
|
48
|
+
addopts = "-ra"
|
|
49
|
+
testpaths = ["tests"]
|
|
50
|
+
|
|
51
|
+
[tool.ruff]
|
|
52
|
+
line-length = 100
|
|
53
|
+
target-version = "py310"
|
|
54
|
+
|
|
55
|
+
[tool.ruff.lint]
|
|
56
|
+
select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
|
|
57
|
+
|
|
58
|
+
[tool.mypy]
|
|
59
|
+
python_version = "3.10"
|
|
60
|
+
strict = true
|
|
61
|
+
packages = ["newapi"]
|
|
62
|
+
mypy_path = "src"
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from ._client import NewAPI
|
|
2
|
+
from ._exceptions import (
|
|
3
|
+
APIError,
|
|
4
|
+
AuthenticationError,
|
|
5
|
+
ConfigurationError,
|
|
6
|
+
ConflictError,
|
|
7
|
+
NewAPIError,
|
|
8
|
+
NotFoundError,
|
|
9
|
+
PermissionDeniedError,
|
|
10
|
+
ProtocolError,
|
|
11
|
+
RateLimitError,
|
|
12
|
+
TransportError,
|
|
13
|
+
TwoFactorRequired,
|
|
14
|
+
ValidationError,
|
|
15
|
+
)
|
|
16
|
+
from ._models import (
|
|
17
|
+
Announcement,
|
|
18
|
+
APIKey,
|
|
19
|
+
Balance,
|
|
20
|
+
CheckinResult,
|
|
21
|
+
CheckinStatus,
|
|
22
|
+
Group,
|
|
23
|
+
Log,
|
|
24
|
+
LoginSession,
|
|
25
|
+
Page,
|
|
26
|
+
PaymentConfig,
|
|
27
|
+
PaymentLink,
|
|
28
|
+
Redemption,
|
|
29
|
+
Resource,
|
|
30
|
+
SessionTokens,
|
|
31
|
+
Subscription,
|
|
32
|
+
SubscriptionPlan,
|
|
33
|
+
Token,
|
|
34
|
+
TopUpOrder,
|
|
35
|
+
UsageRecord,
|
|
36
|
+
User,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"APIError",
|
|
41
|
+
"APIKey",
|
|
42
|
+
"Announcement",
|
|
43
|
+
"AuthenticationError",
|
|
44
|
+
"Balance",
|
|
45
|
+
"CheckinResult",
|
|
46
|
+
"CheckinStatus",
|
|
47
|
+
"ConfigurationError",
|
|
48
|
+
"ConflictError",
|
|
49
|
+
"Group",
|
|
50
|
+
"Log",
|
|
51
|
+
"LoginSession",
|
|
52
|
+
"NewAPI",
|
|
53
|
+
"NewAPIError",
|
|
54
|
+
"NotFoundError",
|
|
55
|
+
"Page",
|
|
56
|
+
"PaymentConfig",
|
|
57
|
+
"PaymentLink",
|
|
58
|
+
"PermissionDeniedError",
|
|
59
|
+
"ProtocolError",
|
|
60
|
+
"RateLimitError",
|
|
61
|
+
"Redemption",
|
|
62
|
+
"Resource",
|
|
63
|
+
"SessionTokens",
|
|
64
|
+
"Subscription",
|
|
65
|
+
"SubscriptionPlan",
|
|
66
|
+
"Token",
|
|
67
|
+
"TopUpOrder",
|
|
68
|
+
"TransportError",
|
|
69
|
+
"TwoFactorRequired",
|
|
70
|
+
"UsageRecord",
|
|
71
|
+
"User",
|
|
72
|
+
"ValidationError",
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
__version__ = "0.1.0"
|