@layervai/qurl 0.2.0
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.
- package/LICENSE +21 -0
- package/README.md +223 -0
- package/dist/cjs/client.d.ts +372 -0
- package/dist/cjs/client.js +2204 -0
- package/dist/cjs/errors.d.ts +125 -0
- package/dist/cjs/errors.js +208 -0
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.js +25 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/types.d.ts +765 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/version.d.ts +1 -0
- package/dist/cjs/version.js +4 -0
- package/dist/esm/client.d.ts +373 -0
- package/dist/esm/client.d.ts.map +1 -0
- package/dist/esm/client.js +2201 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/errors.d.ts +126 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +195 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/types.d.ts +766 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/version.d.ts +2 -0
- package/dist/esm/version.d.ts.map +1 -0
- package/dist/esm/version.js +2 -0
- package/dist/esm/version.js.map +1 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LayerV, Inc.
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# @layervai/qurl
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@layervai/qurl)
|
|
4
|
+
[](https://github.com/layervai/qurl-typescript/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
TypeScript SDK for the [qURL™ API](https://docs.layerv.ai) — secure, time-limited access links for AI agents.
|
|
8
|
+
|
|
9
|
+
> **Quantum URL (qURL)** · The internet has a hidden layer. This is how you enter.
|
|
10
|
+
|
|
11
|
+
## Why qURL?
|
|
12
|
+
|
|
13
|
+
AI agents need to access protected resources — APIs, databases, internal tools — but giving them permanent credentials is a security risk. qURL creates time-limited, auditable access links that expire automatically. The SDK handles authentication, retries, pagination, and error handling so you can focus on your agent logic.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @layervai/qurl
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Requires Node.js 18+. Both `import { QURLClient } from '@layervai/qurl'` (ESM) and `const { QURLClient } = require('@layervai/qurl')` (CJS) work.
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { QURLClient } from '@layervai/qurl';
|
|
27
|
+
|
|
28
|
+
const client = new QURLClient({ apiKey: 'lv_live_xxx' });
|
|
29
|
+
|
|
30
|
+
// Create a protected link
|
|
31
|
+
const result = await client.create({
|
|
32
|
+
target_url: 'https://api.example.com/data',
|
|
33
|
+
expires_in: '24h',
|
|
34
|
+
label: 'API access for agent',
|
|
35
|
+
});
|
|
36
|
+
console.log(result.qurl_link);
|
|
37
|
+
|
|
38
|
+
// Resolve a token (grants network access for your IP)
|
|
39
|
+
const access = await client.resolve('at_...');
|
|
40
|
+
console.log(`Access granted to ${access.target_url} for ${access.access_grant?.expires_in}s`);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## API
|
|
44
|
+
|
|
45
|
+
### `new QURLClient(options)`
|
|
46
|
+
|
|
47
|
+
| Option | Required | Default |
|
|
48
|
+
|--------|----------|---------|
|
|
49
|
+
| `apiKey` | Yes | — |
|
|
50
|
+
| `baseUrl` | No | `https://api.layerv.ai` |
|
|
51
|
+
| `maxRetries` | No | `3` |
|
|
52
|
+
| `timeout` | No | `30000` (ms) — *per attempt*, not total |
|
|
53
|
+
| `fetch` | No | `globalThis.fetch` |
|
|
54
|
+
| `userAgent` | No | `qurl-typescript/<version>` |
|
|
55
|
+
| `debug` | No | `false` |
|
|
56
|
+
|
|
57
|
+
### Methods
|
|
58
|
+
|
|
59
|
+
| Method | Description |
|
|
60
|
+
|--------|-------------|
|
|
61
|
+
| `create(input)` | Create a protected link |
|
|
62
|
+
| `batchCreate(input)` | Create up to 100 protected links in one request |
|
|
63
|
+
| `get(id)` | Get qURL details |
|
|
64
|
+
| `list(input?)` | List qURLs (single page) |
|
|
65
|
+
| `listAll(input?)` | Iterate all qURLs (auto-paginating) |
|
|
66
|
+
| `delete(id)` | Revoke a qURL resource and all its tokens |
|
|
67
|
+
| `extend(id, input)` | Extend expiration |
|
|
68
|
+
| `update(id, input)` | Update qURL resource properties |
|
|
69
|
+
| `mintLink(id, input?)` | Mint a new access link |
|
|
70
|
+
| `resolve(input)` | Resolve token + grant network access |
|
|
71
|
+
| `getQuota()` | Get quota/usage info |
|
|
72
|
+
| `bootstrapAgent(input)` | Bootstrap a qURL Connector agent |
|
|
73
|
+
| `listResources(input?)` / `listAllResources(input?)` / `createResource(input)` / `getResource(id)` | Resource management |
|
|
74
|
+
| `updateResource(id, input)` / `deleteResource(id)` | Update or revoke resources |
|
|
75
|
+
| `createQurlForResource(id, input?)` | Mint a qURL for an existing resource |
|
|
76
|
+
| `updateResourceQurl(id, qurlId, input)` / `revokeResourceQurl(id, qurlId)` | Manage one token on a resource |
|
|
77
|
+
| `listResourceSessions(id)` / `terminateAllResourceSessions(id)` / `terminateResourceSession(id, sessionId)` | Inspect or terminate active sessions |
|
|
78
|
+
| `listConnectorInstallations(input?)` / `listAllConnectorInstallations(input?)` | List connector installations |
|
|
79
|
+
| `getUsageCurrentPeriod()` / `getUsageDaily()` | Usage reporting |
|
|
80
|
+
| `getCustomer()` / `updateCustomer(input)` | Customer settings |
|
|
81
|
+
| `createBillingCheckout(input)` / `createBillingPortal()` / `listBillingInvoices(input?)` / `listAllBillingInvoices(input?)` | Billing flows |
|
|
82
|
+
| `registerDomain(input)` / `listDomains(input?)` / `listAllDomains(input?)` / `getDomain(domain)` | Custom domain management |
|
|
83
|
+
| `verifyDomain(domain)` / `regenerateDomainToken(domain)` / `deleteDomain(domain)` | Domain verification and removal |
|
|
84
|
+
| `listWebhooks(input?)` / `listAllWebhooks(input?)` / `createWebhook(input)` / `getWebhook(id)` | Webhook management |
|
|
85
|
+
| `updateWebhook(id, input)` / `deleteWebhook(id)` / `regenerateWebhookSecret(id)` | Webhook updates and secret rotation |
|
|
86
|
+
| `listWebhookEventTypes()` / `listWebhookDeliveries(id, input?)` / `listAllWebhookDeliveries(id, input?)` | Webhook metadata and delivery history |
|
|
87
|
+
| `createApiKey(input)` / `listApiKeys(input?)` / `listAllApiKeys(input?)` / `updateApiKey(id, input)` / `revokeApiKey(id)` | API key management |
|
|
88
|
+
| `createAccessCode(input)` / `listAccessCodes()` / `redeemAccessCode(input)` / `revokeAccessCode(id)` | Access code management |
|
|
89
|
+
|
|
90
|
+
`listResourceSessions(id)` and `listAccessCodes()` reflect currently unpaginated service endpoints. Their outputs always return `has_more: false`; if the service starts surfacing cursor metadata, the SDK emits a debug log rather than exposing an unactionable next-page signal.
|
|
91
|
+
|
|
92
|
+
`listAll*()` methods validate ids and query params when called, before the async iterator is consumed. Wrap the `listAll*()` call itself in `try/catch` when passing dynamic input.
|
|
93
|
+
|
|
94
|
+
### `batchCreate(input)`
|
|
95
|
+
|
|
96
|
+
Create up to 100 qURLs in a single request. **Does not throw on partial or total failure** — per-item errors are returned in the `results` array, so `try/catch` alone won't surface them. Always inspect `result.failed` and iterate `result.results`:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
const result = await client.batchCreate({
|
|
100
|
+
items: [
|
|
101
|
+
{ target_url: 'https://api.example.com/data', expires_in: '24h' },
|
|
102
|
+
{ target_url: 'https://api.example.com/admin', expires_in: '1h' },
|
|
103
|
+
],
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (result.failed > 0) {
|
|
107
|
+
for (const r of result.results) {
|
|
108
|
+
if (!r.success) {
|
|
109
|
+
console.error(`items[${r.index}]: ${r.error.code} - ${r.error.message}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Non-400 errors (401, 403, 429, 5xx, and unexpected 400 body shapes) still throw the appropriate `QURLError` subclass.
|
|
116
|
+
|
|
117
|
+
**Slimmer per-item shape** — `BatchItemSuccess` returns `{ resource_id, qurl_link, qurl_site, expires_at? }` per item. Unlike single `client.create()`, the batch response intentionally **omits `qurl_id` and `label`** to keep the payload compact. If you migrate a per-item `create()` loop to `batchCreate` and rely on `qurl_id` for downstream addressing, fetch each via `client.get(resource_id)` after the batch (or stay on the single-create path).
|
|
118
|
+
|
|
119
|
+
**Result ordering** — `result.results` is **not** guaranteed to be sorted by `index`. Each entry's `index` field carries the position in the original `items` array, so build per-input-position state by keying on `r.index` (e.g., `for (const r of result.results) { byInputIndex[r.index] = r; }`) rather than relying on iteration order.
|
|
120
|
+
|
|
121
|
+
**Out-of-range or duplicate `index` values** — the SDK throws `QURLError` (`code: "unexpected_response"`) on either condition, since both indicate server misbehavior that would silently break per-item attribution (a `Map` keyed on `r.index` would last-write-wins, an out-of-range index would attribute to a non-existent slot).
|
|
122
|
+
|
|
123
|
+
## Error Handling
|
|
124
|
+
|
|
125
|
+
All API errors throw typed error subclasses, so you can catch specific failure modes:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import {
|
|
129
|
+
QURLError,
|
|
130
|
+
AuthenticationError,
|
|
131
|
+
NotFoundError,
|
|
132
|
+
RateLimitError,
|
|
133
|
+
ValidationError,
|
|
134
|
+
} from '@layervai/qurl';
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
await client.create({ target_url: '' });
|
|
138
|
+
} catch (err) {
|
|
139
|
+
if (err instanceof ValidationError) {
|
|
140
|
+
console.error('Invalid input:', err.invalidFields);
|
|
141
|
+
} else if (err instanceof RateLimitError) {
|
|
142
|
+
console.error(`Rate limited — retry after ${err.retryAfter}s`);
|
|
143
|
+
} else if (err instanceof AuthenticationError) {
|
|
144
|
+
console.error('Bad API key');
|
|
145
|
+
} else if (err instanceof NotFoundError) {
|
|
146
|
+
console.error('Resource not found');
|
|
147
|
+
} else if (err instanceof QURLError) {
|
|
148
|
+
console.error(`API error [${err.code}]: ${err.detail}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
| Error Class | HTTP Status | When |
|
|
154
|
+
|-------------|-------------|------|
|
|
155
|
+
| `AuthenticationError` | 401 | Invalid or missing API key |
|
|
156
|
+
| `AuthorizationError` | 403 | Key lacks required scope |
|
|
157
|
+
| `NotFoundError` | 404 | Resource doesn't exist |
|
|
158
|
+
| `ValidationError` | 400, 422 | Invalid request body |
|
|
159
|
+
| `RateLimitError` | 429 | Too many requests |
|
|
160
|
+
| `ServerError` | 5xx | Server-side failure |
|
|
161
|
+
| `NetworkError` | — | Connection failure |
|
|
162
|
+
| `TimeoutError` | — | Request exceeded timeout |
|
|
163
|
+
|
|
164
|
+
## Pagination
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// Single page
|
|
168
|
+
const page = await client.list({ limit: 10, status: 'active' });
|
|
169
|
+
|
|
170
|
+
// Auto-paginate through all results
|
|
171
|
+
for await (const qurl of client.listAll({ status: 'active' })) {
|
|
172
|
+
console.log(qurl.resource_id);
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Debug Logging
|
|
177
|
+
|
|
178
|
+
Enable debug output to see all HTTP requests and retries:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// Log to console
|
|
182
|
+
const client = new QURLClient({ apiKey: 'lv_live_xxx', debug: true });
|
|
183
|
+
|
|
184
|
+
// Custom logger
|
|
185
|
+
const client = new QURLClient({
|
|
186
|
+
apiKey: 'lv_live_xxx',
|
|
187
|
+
debug: (message, data) => myLogger.debug(message, data),
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Retry Behavior
|
|
192
|
+
|
|
193
|
+
The client automatically retries failed requests with exponential backoff:
|
|
194
|
+
|
|
195
|
+
- **GET/DELETE**: Retries on 429, 502, 503, 504
|
|
196
|
+
- **POST/PATCH**: Retries status responses only on 429
|
|
197
|
+
- **Network errors**: Always retried; POST/PATCH requests send an `Idempotency-Key` on the first attempt and reuse it on retries
|
|
198
|
+
- **`Retry-After` header**: Honored on 429 and 503 responses (RFC 7231 §7.1.3). Currently the SDK only parses **delta-seconds** values (e.g. `Retry-After: 30`); HTTP-date values (`Retry-After: Wed, 21 Oct 2026 07:28:00 GMT`) silently fall back to exponential backoff. Tracked in [#61](https://github.com/layervai/qurl-typescript/issues/61).
|
|
199
|
+
|
|
200
|
+
Configure with `maxRetries` (default: 3). Set to `0` to disable.
|
|
201
|
+
|
|
202
|
+
> **Worst-case latency**: `timeout` is enforced per *attempt*, not for the whole request. Total worst-case latency is roughly `timeout × (maxRetries + 1) + sum(retry delays)`. Operators tuning `timeout` should account for this when sizing health-check budgets.
|
|
203
|
+
|
|
204
|
+
For POST/PATCH requests, the SDK generates a UUIDv7 `Idempotency-Key` once per logical call and reuses it across SDK-managed retries, so the API can return the original result instead of creating duplicate resources. If your application catches an error and calls the SDK again, pass a stable override so the new call deduplicates with the first one. Caller-provided keys must be non-empty printable ASCII strings of at most 256 characters and must not start or end with spaces. Use a unique key for each logical operation; reusing one key for a different request can return the first cached response. To tie retries to your own upstream job or request ID, pass a per-call override:
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
await client.create(
|
|
208
|
+
{ target_url: 'https://api.example.com/data' },
|
|
209
|
+
{ idempotencyKey: 'job_12345_create_qurl' },
|
|
210
|
+
);
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
SDK-generated keys require `globalThis.crypto.getRandomValues`, which is available in supported Node 20+ runtimes and modern edge/browser runtimes. In constrained runtimes without Web Crypto, pass a caller-provided key with `idempotencyKey`; otherwise POST/PATCH calls throw `RuntimeError` before sending a request.
|
|
214
|
+
|
|
215
|
+
## Versioning & breaking changes
|
|
216
|
+
|
|
217
|
+
This SDK is pre-1.0; breaking changes between minor versions are possible until the API surface stabilizes. Significant changes are called out in [`CHANGELOG.md`](CHANGELOG.md) and in the corresponding GitHub release notes.
|
|
218
|
+
|
|
219
|
+
When upgrading, check the release notes for migration guidance — recent breaking changes have included field renames (`description` → `label` on create), removed fields (`metadata`), narrowed type unions (`QURL.status`), and endpoint relocations (`/v1/qurl` → `/v1/qurls`).
|
|
220
|
+
|
|
221
|
+
## License
|
|
222
|
+
|
|
223
|
+
MIT
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import type { AccessCodeListOutput, AgentBootstrapInput, AgentBootstrapOutput, ApiKey, ApiKeyListOutput, BillingInvoiceListOutput, BatchCreateInput, BatchCreateOutput, ClientOptions, ConnectorInstallation, ConnectorInstallationListOutput, CreateAccessCodeInput, CreateAccessCodeOutput, CreateApiKeyInput, CreateApiKeyOutput, CreateBillingCheckoutInput, CreateInput, CreateOutput, CreateQurlForResourceInput, CreateResourceInput, CreateWebhookInput, Customer, CheckoutSession, Domain, DomainListOutput, DomainVerifyResult, ExtendInput, Invoice, ListInput, ListOutput, ListApiKeysInput, ListBillingInvoicesInput, ListConnectorInstallationsInput, ListDomainsInput, ListWebhookDeliveriesInput, ListWebhooksInput, MintInput, MintOutput, PortalSession, QURL, QurlSummary, Quota, RedeemAccessCodeInput, RedeemAccessCodeOutput, RegisterDomainInput, Resource, ResourceDetail, ResourceListInput, ResourceListOutput, RequestOptions, ResolveInput, ResolveOutput, SessionListOutput, SessionTerminateOutput, UpdateApiKeyInput, UpdateCustomerInput, UpdateInput, UpdateResourceInput, UpdateResourceQurlInput, UpdateWebhookInput, UsageCurrentPeriod, UsageDaily, Webhook, WebhookDelivery, WebhookDeliveryListOutput, WebhookEventTypeInfo, WebhookListOutput, WebhookWithSecret } from "./types.js";
|
|
2
|
+
/** qURL API client. */
|
|
3
|
+
export declare class QURLClient {
|
|
4
|
+
private readonly baseUrl;
|
|
5
|
+
private readonly apiKey;
|
|
6
|
+
private readonly fetchFn;
|
|
7
|
+
private readonly maxRetries;
|
|
8
|
+
private readonly timeout;
|
|
9
|
+
private readonly userAgent;
|
|
10
|
+
private readonly debugFn;
|
|
11
|
+
constructor(options: ClientOptions);
|
|
12
|
+
/** Returns a JSON-safe representation with the API key masked. */
|
|
13
|
+
toJSON(): {
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
apiKey: string;
|
|
16
|
+
};
|
|
17
|
+
private maskKey;
|
|
18
|
+
private log;
|
|
19
|
+
/**
|
|
20
|
+
* Map the API's wire-format `qurls` field to the SDK's `access_tokens`.
|
|
21
|
+
*
|
|
22
|
+
* Uses `hasOwnProperty.call` (not `if (raw.qurls)`) so empty `qurls: []`
|
|
23
|
+
* still enters the rename branch — consumers expect `access_tokens: []`,
|
|
24
|
+
* not `qurls: []` leaking through. `.call` form confines the check to
|
|
25
|
+
* own-properties; safe even on pre-parsed bodies from arbitrary sources.
|
|
26
|
+
*/
|
|
27
|
+
private mapQurlsField;
|
|
28
|
+
/**
|
|
29
|
+
* Validate the response envelope from POST /v1/qurls/batch and return
|
|
30
|
+
* the inner {@link BatchCreateOutput}. Throws
|
|
31
|
+
* {@link unexpectedResponseError} on any shape-guard failure.
|
|
32
|
+
*
|
|
33
|
+
* Three layers of defense:
|
|
34
|
+
* 1. Top-level shape (succeeded/failed are non-negative integers;
|
|
35
|
+
* results is an array).
|
|
36
|
+
* 2. Arithmetic invariants: `succeeded + failed === results.length`
|
|
37
|
+
* and `results.length === requestItemCount` (catches silent
|
|
38
|
+
* server-side item drops).
|
|
39
|
+
* 3. Per-entry discriminated-union contract + index range / dedupe.
|
|
40
|
+
*
|
|
41
|
+
* Error messages include the observed HTTP status as a suffix so
|
|
42
|
+
* operators can attribute drift; entry values are deliberately
|
|
43
|
+
* never echoed (unexpected bodies may carry sensitive data).
|
|
44
|
+
*
|
|
45
|
+
* **Fail-fast, unlike `validateCreateInput`'s collect-all.** This is
|
|
46
|
+
* a server-contract integrity check, not user input — once the envelope
|
|
47
|
+
* is malformed, additional shape errors don't help debug the upstream
|
|
48
|
+
* issue.
|
|
49
|
+
*/
|
|
50
|
+
private validateBatchCreateResponse;
|
|
51
|
+
private paginateAll;
|
|
52
|
+
/** Create a new qURL. */
|
|
53
|
+
create(input: CreateInput, options?: RequestOptions): Promise<CreateOutput>;
|
|
54
|
+
/**
|
|
55
|
+
* Batch create multiple qURLs (1-`MAX_BATCH_ITEMS` items).
|
|
56
|
+
*
|
|
57
|
+
* **Partial failures do not throw.** Two paths resolve normally with
|
|
58
|
+
* structured per-item results:
|
|
59
|
+
* - **HTTP 207 Multi-Status** (some succeeded, some failed) — passes the
|
|
60
|
+
* `response.ok` check naturally since 207 is in the 2xx range.
|
|
61
|
+
* - **HTTP 400** (every item failed validation) — the API returns a
|
|
62
|
+
* populated `BatchCreateOutput` body in this case; whitelisted via
|
|
63
|
+
* `passthroughStatuses` so the structured per-item errors aren't
|
|
64
|
+
* swallowed by the generic error path.
|
|
65
|
+
*
|
|
66
|
+
* Callers that only catch thrown errors will **silently miss partial
|
|
67
|
+
* failures**. Always branch on `result.failed > 0` before treating the
|
|
68
|
+
* call as successful. Other error statuses (401, 403, 429, 5xx) still
|
|
69
|
+
* throw the appropriate `QURLError` subclass.
|
|
70
|
+
*
|
|
71
|
+
* **Client-side validation:** All items are validated before any network
|
|
72
|
+
* request is made (target_url scheme, label length, max_sessions range,
|
|
73
|
+
* tag constraints). All failures are collected into a single
|
|
74
|
+
* `ValidationError` with per-index attribution
|
|
75
|
+
* (`"items[0]: ... | items[3]: ..."`) — items are separated by ` | ` and
|
|
76
|
+
* field-level errors within an item by `; ` so callers can split the
|
|
77
|
+
* detail message reliably. Every problem surfaces in one throw instead
|
|
78
|
+
* of fix-re-run-repeat.
|
|
79
|
+
*
|
|
80
|
+
* Throws `ValidationError` client-side (`status: 0`, `code: "client_validation"`)
|
|
81
|
+
* when `items` is empty or exceeds `MAX_BATCH_ITEMS`, or when the
|
|
82
|
+
* HTTP 400 response body doesn't match the expected `BatchCreateOutput`
|
|
83
|
+
* shape (defense-in-depth for cases where the endpoint returns a non-batch
|
|
84
|
+
* error on 400).
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* Always check `result.failed` after the call — exceptions alone are
|
|
88
|
+
* not enough, because 207 / 400 partial failures resolve normally:
|
|
89
|
+
* ```ts
|
|
90
|
+
* const result = await client.batchCreate({ items });
|
|
91
|
+
*
|
|
92
|
+
* if (result.failed > 0) {
|
|
93
|
+
* console.warn(`${result.failed}/${result.results.length} items failed`);
|
|
94
|
+
* }
|
|
95
|
+
*
|
|
96
|
+
* // Discriminated union on `success` lets you narrow per-item:
|
|
97
|
+
* for (const r of result.results) {
|
|
98
|
+
* if (r.success) {
|
|
99
|
+
* handleOk(r.resource_id, r.qurl_link);
|
|
100
|
+
* } else {
|
|
101
|
+
* handleErr(r.index, r.error.code, r.error.message);
|
|
102
|
+
* }
|
|
103
|
+
* }
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
batchCreate(input: BatchCreateInput, options?: RequestOptions): Promise<BatchCreateOutput>;
|
|
107
|
+
/**
|
|
108
|
+
* Get a qURL resource and its access tokens.
|
|
109
|
+
*
|
|
110
|
+
* Accepts either a resource ID (`r_` prefix) or a qURL display ID (`q_`
|
|
111
|
+
* prefix); the API resolves `q_` IDs to the parent resource automatically.
|
|
112
|
+
*/
|
|
113
|
+
get(id: string): Promise<QURL>;
|
|
114
|
+
/**
|
|
115
|
+
* Lists protected URLs. Each qURL groups access tokens sharing the same target URL.
|
|
116
|
+
* Note: list items include qurl_count but not access_tokens (too expensive at scale).
|
|
117
|
+
*
|
|
118
|
+
* **`limit` semantics:** `limit` is validated client-side to be in
|
|
119
|
+
* the range `[1, 100]` per the OpenAPI spec (`GET /v1/qurls` defines
|
|
120
|
+
* `limit: integer, minimum: 1, maximum: 100, default: 20`). Passing
|
|
121
|
+
* a value outside that range throws `ValidationError` before the
|
|
122
|
+
* request is issued. Omitting `limit` lets the server apply its
|
|
123
|
+
* default page size (currently 20). Matches the client-side
|
|
124
|
+
* validation style used for `max_sessions`, `tag count`, URL length.
|
|
125
|
+
*/
|
|
126
|
+
list(input?: ListInput): Promise<ListOutput>;
|
|
127
|
+
/**
|
|
128
|
+
* Iterate over all qURLs, automatically paginating.
|
|
129
|
+
*
|
|
130
|
+
* Termination honors explicit `has_more: false` first, then falls back
|
|
131
|
+
* to the cursor shape. A missing `next_cursor` still terminates even if
|
|
132
|
+
* a buggy response claimed `has_more: true`, and a stale cursor with
|
|
133
|
+
* `has_more: false` does not trigger an extra round-trip.
|
|
134
|
+
*
|
|
135
|
+
* Input is validated synchronously when this method is called, before
|
|
136
|
+
* iteration begins.
|
|
137
|
+
*/
|
|
138
|
+
listAll(input?: Omit<ListInput, "cursor">): AsyncGenerator<QURL, void, undefined>;
|
|
139
|
+
/**
|
|
140
|
+
* Delete (revoke) a qURL resource and all its access tokens.
|
|
141
|
+
*
|
|
142
|
+
* Only accepts a resource ID (`r_` prefix), not a qURL display ID (`q_`
|
|
143
|
+
* prefix). Per the OpenAPI spec: *"Requires a resource ID (r_ prefix).
|
|
144
|
+
* To revoke a single token, use DELETE /v1/resources/:id/qurls/:qurl_id"*.
|
|
145
|
+
* A client-side prefix check catches the mistake before the API round-trip.
|
|
146
|
+
*/
|
|
147
|
+
delete(id: string): Promise<void>;
|
|
148
|
+
/**
|
|
149
|
+
* Extend a qURL's expiration.
|
|
150
|
+
*
|
|
151
|
+
* Accepts either a resource ID (`r_` prefix) or a qURL display ID (`q_`
|
|
152
|
+
* prefix). Convenience method — delegates to {@link update} with only the
|
|
153
|
+
* expiration fields. `ExtendInput` shares its `extend_by` / `expires_at`
|
|
154
|
+
* fields with `UpdateInput` but is *narrower in two ways*: (1) exactly
|
|
155
|
+
* one of the two must be present (XOR via `?: never`), where `UpdateInput`
|
|
156
|
+
* allows neither; and (2) `description` / `tags` are absent.
|
|
157
|
+
*
|
|
158
|
+
* Destructuring before delegation strips wider fields at runtime, so an
|
|
159
|
+
* untyped-JS caller spreading `{ description, tags, ... }` can't leak
|
|
160
|
+
* those through this path. The XOR (exactly-one-of-two) invariant is
|
|
161
|
+
* enforced separately by the runtime check inside `update()`.
|
|
162
|
+
*/
|
|
163
|
+
extend(id: string, input: ExtendInput, options?: RequestOptions): Promise<QURL>;
|
|
164
|
+
/**
|
|
165
|
+
* Update a qURL — extend expiration, change description, rename tags.
|
|
166
|
+
*
|
|
167
|
+
* Accepts either a resource ID (`r_` prefix) or a qURL display ID (`q_`
|
|
168
|
+
* prefix); the API resolves `q_` IDs to the parent resource automatically.
|
|
169
|
+
*/
|
|
170
|
+
update(id: string, input: UpdateInput, options?: RequestOptions): Promise<QURL>;
|
|
171
|
+
/**
|
|
172
|
+
* Mint a new access link for a qURL.
|
|
173
|
+
*
|
|
174
|
+
* Accepts either a resource ID (`r_` prefix) or a qURL display ID (`q_`
|
|
175
|
+
* prefix); the API resolves `q_` IDs to the parent resource automatically.
|
|
176
|
+
*
|
|
177
|
+
* Passing `{}` or an object with all fields `null`/`undefined` is
|
|
178
|
+
* equivalent to omitting the second argument: no body is sent, and the
|
|
179
|
+
* server applies its 24h default expiration.
|
|
180
|
+
*/
|
|
181
|
+
mintLink(id: string, input?: MintInput, options?: RequestOptions): Promise<MintOutput>;
|
|
182
|
+
/**
|
|
183
|
+
* Resolve a qURL access token (headless).
|
|
184
|
+
*
|
|
185
|
+
* Triggers an NHP knock to grant network access for the caller's IP.
|
|
186
|
+
* Requires `qurl:resolve` scope on the API key.
|
|
187
|
+
*
|
|
188
|
+
* Accepts a plain token string or a `ResolveInput` object.
|
|
189
|
+
*/
|
|
190
|
+
resolve(input: ResolveInput | string, options?: RequestOptions): Promise<ResolveOutput>;
|
|
191
|
+
/** Get quota and usage information. */
|
|
192
|
+
getQuota(): Promise<Quota>;
|
|
193
|
+
/** Bootstrap a LayerV qURL Connector agent. */
|
|
194
|
+
bootstrapAgent(input: AgentBootstrapInput, options?: RequestOptions): Promise<AgentBootstrapOutput>;
|
|
195
|
+
/** List resources from the `/v1/resources` API. */
|
|
196
|
+
listResources(input?: ResourceListInput): Promise<ResourceListOutput>;
|
|
197
|
+
/**
|
|
198
|
+
* Iterate all resources, automatically paginating.
|
|
199
|
+
*
|
|
200
|
+
* Input is validated synchronously when this method is called, before
|
|
201
|
+
* iteration begins.
|
|
202
|
+
*/
|
|
203
|
+
listAllResources(input?: Omit<ResourceListInput, "cursor">): AsyncGenerator<Resource, void, undefined>;
|
|
204
|
+
/** Create a resource directly. */
|
|
205
|
+
createResource(input: CreateResourceInput, options?: RequestOptions): Promise<Resource>;
|
|
206
|
+
/** Get one resource plus its bounded qURL preview. */
|
|
207
|
+
getResource(id: string): Promise<ResourceDetail>;
|
|
208
|
+
/** Update resource metadata. */
|
|
209
|
+
updateResource(id: string, input: UpdateResourceInput, options?: RequestOptions): Promise<Resource>;
|
|
210
|
+
/** Revoke a resource and all of its qURLs. */
|
|
211
|
+
deleteResource(id: string): Promise<void>;
|
|
212
|
+
/** Mint a qURL against an existing resource. */
|
|
213
|
+
createQurlForResource(id: string, input?: CreateQurlForResourceInput, options?: RequestOptions): Promise<CreateOutput>;
|
|
214
|
+
/** Revoke a specific qURL token on a resource. */
|
|
215
|
+
revokeResourceQurl(id: string, qurlId: string): Promise<void>;
|
|
216
|
+
/** Update a specific qURL token on a resource. */
|
|
217
|
+
updateResourceQurl(id: string, qurlId: string, input: UpdateResourceQurlInput, options?: RequestOptions): Promise<QurlSummary>;
|
|
218
|
+
/**
|
|
219
|
+
* List active access sessions for a resource.
|
|
220
|
+
*
|
|
221
|
+
* The current API contract has no cursor query params for this endpoint.
|
|
222
|
+
* If the service later advertises pagination metadata, the SDK returns
|
|
223
|
+
* this page and emits a debug log rather than pretending it fetched all pages.
|
|
224
|
+
*/
|
|
225
|
+
listResourceSessions(id: string): Promise<SessionListOutput>;
|
|
226
|
+
/**
|
|
227
|
+
* Terminate all active sessions for a resource.
|
|
228
|
+
*
|
|
229
|
+
* The returned count is best-effort under retries: if the first DELETE
|
|
230
|
+
* succeeds server-side but the response is lost, a retried request may
|
|
231
|
+
* return `0` because there are no sessions left to terminate.
|
|
232
|
+
*/
|
|
233
|
+
terminateAllResourceSessions(id: string): Promise<SessionTerminateOutput>;
|
|
234
|
+
/** Terminate a specific resource session. */
|
|
235
|
+
terminateResourceSession(id: string, sessionId: string): Promise<void>;
|
|
236
|
+
/** List connector installations. */
|
|
237
|
+
listConnectorInstallations(input?: ListConnectorInstallationsInput): Promise<ConnectorInstallationListOutput>;
|
|
238
|
+
/**
|
|
239
|
+
* Iterate all connector installations, automatically paginating.
|
|
240
|
+
*
|
|
241
|
+
* Input is validated synchronously when this method is called, before
|
|
242
|
+
* iteration begins.
|
|
243
|
+
*/
|
|
244
|
+
listAllConnectorInstallations(input?: Omit<ListConnectorInstallationsInput, "cursor">): AsyncGenerator<ConnectorInstallation, void, undefined>;
|
|
245
|
+
/** Get current-period usage. */
|
|
246
|
+
getUsageCurrentPeriod(): Promise<UsageCurrentPeriod>;
|
|
247
|
+
/** Get daily usage for the current period. */
|
|
248
|
+
getUsageDaily(): Promise<UsageDaily>;
|
|
249
|
+
/** Get the customer profile. */
|
|
250
|
+
getCustomer(): Promise<Customer>;
|
|
251
|
+
/** Update customer settings. */
|
|
252
|
+
updateCustomer(input: UpdateCustomerInput, options?: RequestOptions): Promise<Customer>;
|
|
253
|
+
/** Create a Stripe checkout session. */
|
|
254
|
+
createBillingCheckout(input: CreateBillingCheckoutInput, options?: RequestOptions): Promise<CheckoutSession>;
|
|
255
|
+
/** Create a Stripe billing portal session. */
|
|
256
|
+
createBillingPortal(options?: RequestOptions): Promise<PortalSession>;
|
|
257
|
+
/** List billing invoices. */
|
|
258
|
+
listBillingInvoices(input?: ListBillingInvoicesInput): Promise<BillingInvoiceListOutput>;
|
|
259
|
+
/**
|
|
260
|
+
* Iterate all billing invoices, automatically paginating.
|
|
261
|
+
*
|
|
262
|
+
* Input is validated synchronously when this method is called, before
|
|
263
|
+
* iteration begins.
|
|
264
|
+
*/
|
|
265
|
+
listAllBillingInvoices(input?: Omit<ListBillingInvoicesInput, "cursor">): AsyncGenerator<Invoice, void, undefined>;
|
|
266
|
+
/** Register a custom domain. */
|
|
267
|
+
registerDomain(input: RegisterDomainInput, options?: RequestOptions): Promise<Domain>;
|
|
268
|
+
/** List custom domains. */
|
|
269
|
+
listDomains(input?: ListDomainsInput): Promise<DomainListOutput>;
|
|
270
|
+
/**
|
|
271
|
+
* Iterate all domains, automatically paginating.
|
|
272
|
+
*
|
|
273
|
+
* Input is validated synchronously when this method is called, before
|
|
274
|
+
* iteration begins.
|
|
275
|
+
*/
|
|
276
|
+
listAllDomains(input?: Omit<ListDomainsInput, "cursor">): AsyncGenerator<Domain, void, undefined>;
|
|
277
|
+
/** Get custom domain status. */
|
|
278
|
+
getDomain(domain: string): Promise<Domain>;
|
|
279
|
+
/** Remove a custom domain. */
|
|
280
|
+
deleteDomain(domain: string): Promise<void>;
|
|
281
|
+
/** Trigger DNS verification for a custom domain. */
|
|
282
|
+
verifyDomain(domain: string, options?: RequestOptions): Promise<DomainVerifyResult>;
|
|
283
|
+
/** Regenerate a domain verification token. */
|
|
284
|
+
regenerateDomainToken(domain: string, options?: RequestOptions): Promise<Domain>;
|
|
285
|
+
/** List webhooks. */
|
|
286
|
+
listWebhooks(input?: ListWebhooksInput): Promise<WebhookListOutput>;
|
|
287
|
+
/**
|
|
288
|
+
* Iterate all webhooks, automatically paginating.
|
|
289
|
+
*
|
|
290
|
+
* Input is validated synchronously when this method is called, before
|
|
291
|
+
* iteration begins.
|
|
292
|
+
*/
|
|
293
|
+
listAllWebhooks(input?: Omit<ListWebhooksInput, "cursor">): AsyncGenerator<Webhook, void, undefined>;
|
|
294
|
+
/** Create a webhook. */
|
|
295
|
+
createWebhook(input: CreateWebhookInput, options?: RequestOptions): Promise<WebhookWithSecret>;
|
|
296
|
+
/** List available webhook event types. */
|
|
297
|
+
listWebhookEventTypes(): Promise<WebhookEventTypeInfo[]>;
|
|
298
|
+
/** Get a webhook. */
|
|
299
|
+
getWebhook(id: string): Promise<Webhook>;
|
|
300
|
+
/** Update a webhook. */
|
|
301
|
+
updateWebhook(id: string, input: UpdateWebhookInput, options?: RequestOptions): Promise<Webhook>;
|
|
302
|
+
/** Delete a webhook. */
|
|
303
|
+
deleteWebhook(id: string): Promise<void>;
|
|
304
|
+
/** Regenerate a webhook signing secret. */
|
|
305
|
+
regenerateWebhookSecret(id: string, options?: RequestOptions): Promise<WebhookWithSecret>;
|
|
306
|
+
/** List delivery attempts for a webhook. */
|
|
307
|
+
listWebhookDeliveries(id: string, input?: ListWebhookDeliveriesInput): Promise<WebhookDeliveryListOutput>;
|
|
308
|
+
/**
|
|
309
|
+
* Iterate all delivery attempts for a webhook, automatically paginating.
|
|
310
|
+
*
|
|
311
|
+
* Input is validated synchronously when this method is called, before
|
|
312
|
+
* iteration begins.
|
|
313
|
+
*/
|
|
314
|
+
listAllWebhookDeliveries(id: string, input?: Omit<ListWebhookDeliveriesInput, "cursor">): AsyncGenerator<WebhookDelivery, void, undefined>;
|
|
315
|
+
/** Create a new API key. */
|
|
316
|
+
createApiKey(input: CreateApiKeyInput, options?: RequestOptions): Promise<CreateApiKeyOutput>;
|
|
317
|
+
/** List API keys. */
|
|
318
|
+
listApiKeys(input?: ListApiKeysInput): Promise<ApiKeyListOutput>;
|
|
319
|
+
/**
|
|
320
|
+
* Iterate all API keys, automatically paginating.
|
|
321
|
+
*
|
|
322
|
+
* Input is validated synchronously when this method is called, before
|
|
323
|
+
* iteration begins.
|
|
324
|
+
*/
|
|
325
|
+
listAllApiKeys(input?: Omit<ListApiKeysInput, "cursor">): AsyncGenerator<ApiKey, void, undefined>;
|
|
326
|
+
/** Update an API key. */
|
|
327
|
+
updateApiKey(keyId: string, input: UpdateApiKeyInput, options?: RequestOptions): Promise<ApiKey>;
|
|
328
|
+
/** Revoke an API key. */
|
|
329
|
+
revokeApiKey(keyId: string): Promise<void>;
|
|
330
|
+
/** Redeem an access code. */
|
|
331
|
+
redeemAccessCode(input: RedeemAccessCodeInput, options?: RequestOptions): Promise<RedeemAccessCodeOutput>;
|
|
332
|
+
/** Create an access code. */
|
|
333
|
+
createAccessCode(input: CreateAccessCodeInput, options?: RequestOptions): Promise<CreateAccessCodeOutput>;
|
|
334
|
+
/**
|
|
335
|
+
* List access codes.
|
|
336
|
+
*
|
|
337
|
+
* The current API contract has no cursor query params for this endpoint.
|
|
338
|
+
* If the service later advertises pagination metadata, the SDK returns
|
|
339
|
+
* this page and emits a debug log rather than pretending it fetched all pages.
|
|
340
|
+
*/
|
|
341
|
+
listAccessCodes(): Promise<AccessCodeListOutput>;
|
|
342
|
+
/** Revoke an access code. */
|
|
343
|
+
revokeAccessCode(id: string): Promise<void>;
|
|
344
|
+
private request;
|
|
345
|
+
/**
|
|
346
|
+
* Issue an HTTP request and parse the JSON response.
|
|
347
|
+
*
|
|
348
|
+
* `passthroughStatuses` lets a caller opt certain non-2xx codes out of the
|
|
349
|
+
* default throw-on-error path and receive the parsed body instead. This is
|
|
350
|
+
* used by `batchCreate`, where the API returns a structured
|
|
351
|
+
* `BatchCreateOutput` on HTTP 400 (all items rejected) — throwing would
|
|
352
|
+
* drop the per-item errors.
|
|
353
|
+
*/
|
|
354
|
+
private rawRequest;
|
|
355
|
+
private parseError;
|
|
356
|
+
private parseRetryAfter;
|
|
357
|
+
/**
|
|
358
|
+
* Compute the delay before the next retry attempt.
|
|
359
|
+
*
|
|
360
|
+
* - If the server sent a usable `Retry-After`, the value (converted
|
|
361
|
+
* from seconds to ms) is honored without the `RETRY_MAX_DELAY_MS`
|
|
362
|
+
* clamp — the cap exists to bound exponential backoff against
|
|
363
|
+
* ourselves; clamping a server-asserted directive (e.g.
|
|
364
|
+
* `Retry-After: 60`) down to 30s would just re-trip the rate limit.
|
|
365
|
+
* `Retry-After: 0` is honored verbatim per RFC 7231 §7.1.3 ("retry
|
|
366
|
+
* immediately").
|
|
367
|
+
* - Otherwise: `RETRY_BASE_DELAY_MS * 2^(attempt-1)` plus 0-50%
|
|
368
|
+
* jitter, capped at `RETRY_MAX_DELAY_MS`.
|
|
369
|
+
*/
|
|
370
|
+
private retryDelay;
|
|
371
|
+
private classifyFetchError;
|
|
372
|
+
}
|