@myapihq/sdk 2.0.1 → 2.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/dist/auth.d.ts +14 -2
- package/dist/auth.js +24 -0
- package/dist/funds.d.ts +11 -0
- package/dist/funds.js +55 -0
- package/dist/hq.d.ts +22 -0
- package/dist/hq.js +18 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/services.js +2 -2
- package/package.json +1 -1
- package/src/auth.ts +35 -3
- package/src/funds.ts +66 -0
- package/src/hq.ts +59 -0
- package/src/index.ts +1 -0
- package/src/services.ts +2 -2
package/dist/auth.d.ts
CHANGED
|
@@ -32,11 +32,17 @@ export interface AuthUsage {
|
|
|
32
32
|
period: string;
|
|
33
33
|
active_users: number;
|
|
34
34
|
price_cents_each: number;
|
|
35
|
+
as_of?: string;
|
|
35
36
|
}
|
|
36
37
|
export interface AuthDomain {
|
|
37
38
|
domain: string;
|
|
38
|
-
status: 'pending' | 'active';
|
|
39
|
-
|
|
39
|
+
status: 'awaiting_verification' | 'pending' | 'active';
|
|
40
|
+
verification?: {
|
|
41
|
+
type: string;
|
|
42
|
+
name: string;
|
|
43
|
+
value: string;
|
|
44
|
+
};
|
|
45
|
+
dns?: {
|
|
40
46
|
type: string;
|
|
41
47
|
name: string;
|
|
42
48
|
value: string;
|
|
@@ -49,9 +55,15 @@ export declare function createTenant(apiKey: string, orgId: string, input?: Crea
|
|
|
49
55
|
export declare function getTenant(apiKey: string, orgId: string): Promise<Tenant>;
|
|
50
56
|
export declare function createClient(apiKey: string, orgId: string, input: CreateClientInput): Promise<AuthClient>;
|
|
51
57
|
export declare function listClients(apiKey: string, orgId: string): Promise<ListClientsResponse>;
|
|
58
|
+
export declare function deleteClient(apiKey: string, orgId: string, clientId: string): Promise<{
|
|
59
|
+
client_id: string;
|
|
60
|
+
deleted: boolean;
|
|
61
|
+
}>;
|
|
62
|
+
export declare function rotateClient(apiKey: string, orgId: string, clientId: string): Promise<AuthClient>;
|
|
52
63
|
export declare function getUsage(apiKey: string, orgId: string): Promise<AuthUsage>;
|
|
53
64
|
export declare function registerDomain(apiKey: string, orgId: string, domain: string): Promise<AuthDomain>;
|
|
54
65
|
export declare function getDomain(apiKey: string, orgId: string): Promise<AuthDomain>;
|
|
66
|
+
export declare function verifyDomain(apiKey: string, orgId: string): Promise<AuthDomain>;
|
|
55
67
|
export declare function deleteDomain(apiKey: string, orgId: string, domain: string): Promise<{
|
|
56
68
|
domain: string;
|
|
57
69
|
status: string;
|
package/dist/auth.js
CHANGED
|
@@ -5,9 +5,12 @@ exports.createTenant = createTenant;
|
|
|
5
5
|
exports.getTenant = getTenant;
|
|
6
6
|
exports.createClient = createClient;
|
|
7
7
|
exports.listClients = listClients;
|
|
8
|
+
exports.deleteClient = deleteClient;
|
|
9
|
+
exports.rotateClient = rotateClient;
|
|
8
10
|
exports.getUsage = getUsage;
|
|
9
11
|
exports.registerDomain = registerDomain;
|
|
10
12
|
exports.getDomain = getDomain;
|
|
13
|
+
exports.verifyDomain = verifyDomain;
|
|
11
14
|
exports.deleteDomain = deleteDomain;
|
|
12
15
|
const client_1 = require("./client");
|
|
13
16
|
const config_1 = require("./config");
|
|
@@ -29,9 +32,12 @@ exports.EXPOSES = [
|
|
|
29
32
|
'GET /auth/orgs/{org_id}/tenant',
|
|
30
33
|
'POST /auth/orgs/{org_id}/clients',
|
|
31
34
|
'GET /auth/orgs/{org_id}/clients',
|
|
35
|
+
'DELETE /auth/orgs/{org_id}/clients/{client_id}',
|
|
36
|
+
'POST /auth/orgs/{org_id}/clients/{client_id}/rotate',
|
|
32
37
|
'GET /auth/orgs/{org_id}/usage',
|
|
33
38
|
'POST /auth/orgs/{org_id}/domain',
|
|
34
39
|
'GET /auth/orgs/{org_id}/domain',
|
|
40
|
+
'POST /auth/orgs/{org_id}/domain/verify',
|
|
35
41
|
'DELETE /auth/orgs/{org_id}/domain',
|
|
36
42
|
];
|
|
37
43
|
// Create (or update) the org's auth tenant. Idempotent. `connections` selects
|
|
@@ -57,6 +63,17 @@ async function createClient(apiKey, orgId, input) {
|
|
|
57
63
|
async function listClients(apiKey, orgId) {
|
|
58
64
|
return (0, client_1.request)('GET', `${config_1.AUTH_BASE}/auth/orgs/${encodeURIComponent(orgId)}/clients`, apiKey);
|
|
59
65
|
}
|
|
66
|
+
// Delete (revoke) an OIDC client. Irreversible — the client_id stops
|
|
67
|
+
// authenticating immediately.
|
|
68
|
+
async function deleteClient(apiKey, orgId, clientId) {
|
|
69
|
+
return (0, client_1.request)('DELETE', `${config_1.AUTH_BASE}/auth/orgs/${encodeURIComponent(orgId)}/clients/${encodeURIComponent(clientId)}`, apiKey);
|
|
70
|
+
}
|
|
71
|
+
// Rotate a confidential ('web') client's secret. Returns the NEW secret exactly
|
|
72
|
+
// once; the previous secret stops working immediately. (SPA clients have no
|
|
73
|
+
// secret — rotating one is rejected by the backend.)
|
|
74
|
+
async function rotateClient(apiKey, orgId, clientId) {
|
|
75
|
+
return (0, client_1.request)('POST', `${config_1.AUTH_BASE}/auth/orgs/${encodeURIComponent(orgId)}/clients/${encodeURIComponent(clientId)}/rotate`, apiKey);
|
|
76
|
+
}
|
|
60
77
|
// Monthly-active-user usage for the current period (auth is billed per MAU).
|
|
61
78
|
async function getUsage(apiKey, orgId) {
|
|
62
79
|
return (0, client_1.request)('GET', `${config_1.AUTH_BASE}/auth/orgs/${encodeURIComponent(orgId)}/usage`, apiKey);
|
|
@@ -71,6 +88,13 @@ async function registerDomain(apiKey, orgId, domain) {
|
|
|
71
88
|
async function getDomain(apiKey, orgId) {
|
|
72
89
|
return (0, client_1.request)('GET', `${config_1.AUTH_BASE}/auth/orgs/${encodeURIComponent(orgId)}/domain`, apiKey);
|
|
73
90
|
}
|
|
91
|
+
// Verify ownership of the org's custom auth domain by checking the published
|
|
92
|
+
// TXT challenge. On success the domain advances awaiting_verification → pending
|
|
93
|
+
// (and the response carries the A record). Rejects with 422 DOMAIN_NOT_VERIFIED
|
|
94
|
+
// if the TXT record isn't found yet. Idempotent once already pending/active.
|
|
95
|
+
async function verifyDomain(apiKey, orgId) {
|
|
96
|
+
return (0, client_1.request)('POST', `${config_1.AUTH_BASE}/auth/orgs/${encodeURIComponent(orgId)}/domain/verify`, apiKey);
|
|
97
|
+
}
|
|
74
98
|
async function deleteDomain(apiKey, orgId, domain) {
|
|
75
99
|
return (0, client_1.request)('DELETE', `${config_1.AUTH_BASE}/auth/orgs/${encodeURIComponent(orgId)}/domain`, apiKey, { domain });
|
|
76
100
|
}
|
package/dist/funds.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { MyApiError } from './client';
|
|
2
|
+
export type AutoRechargeState = 'in_flight' | 'disabled' | 'capped' | 'no_pm' | 'failed';
|
|
3
|
+
export declare function isInsufficientFunds(err: unknown): err is MyApiError;
|
|
4
|
+
export declare function isSpendCapExceeded(err: unknown): err is MyApiError;
|
|
5
|
+
export declare function autoRechargeState(err: unknown): AutoRechargeState | undefined;
|
|
6
|
+
export interface FundsRetryOptions {
|
|
7
|
+
maxRetries?: number;
|
|
8
|
+
sleep?: (ms: number) => Promise<void>;
|
|
9
|
+
onRetry?: (attempt: number, waitSeconds: number) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function withFundsRetry<T>(call: () => Promise<T>, opts?: FundsRetryOptions): Promise<T>;
|
package/dist/funds.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isInsufficientFunds = isInsufficientFunds;
|
|
4
|
+
exports.isSpendCapExceeded = isSpendCapExceeded;
|
|
5
|
+
exports.autoRechargeState = autoRechargeState;
|
|
6
|
+
exports.withFundsRetry = withFundsRetry;
|
|
7
|
+
const client_1 = require("./client");
|
|
8
|
+
// Wallet empty. Matches the unified `INSUFFICIENT_FUNDS` and the legacy
|
|
9
|
+
// `INSUFFICIENT_BALANCE` (still emitted by imagegen's free-tier path until the
|
|
10
|
+
// backend finishes the split — tolerate both).
|
|
11
|
+
function isInsufficientFunds(err) {
|
|
12
|
+
return err instanceof client_1.MyApiError &&
|
|
13
|
+
(err.code === 'INSUFFICIENT_FUNDS' || err.code === 'INSUFFICIENT_BALANCE');
|
|
14
|
+
}
|
|
15
|
+
// Hard account spend ceiling hit (distinct from an empty wallet). Always needs
|
|
16
|
+
// a human — raising/clearing the cap — never a retry or top-up.
|
|
17
|
+
function isSpendCapExceeded(err) {
|
|
18
|
+
return err instanceof client_1.MyApiError && err.code === 'SPEND_CAP_EXCEEDED';
|
|
19
|
+
}
|
|
20
|
+
// The auto_recharge state on an INSUFFICIENT_FUNDS error, if the backend set it.
|
|
21
|
+
function autoRechargeState(err) {
|
|
22
|
+
if (!(err instanceof client_1.MyApiError))
|
|
23
|
+
return undefined;
|
|
24
|
+
const s = err.body?.auto_recharge;
|
|
25
|
+
return typeof s === 'string' ? s : undefined;
|
|
26
|
+
}
|
|
27
|
+
const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
28
|
+
// Wrap a billable call so an empty wallet with an auto-recharge IN FLIGHT is
|
|
29
|
+
// polled-and-retried instead of failing — the whole point of the 402 split,
|
|
30
|
+
// so an autonomous agent doesn't escalate to a human for a refill that's
|
|
31
|
+
// already coming. Retries ONLY on `INSUFFICIENT_FUNDS` + `auto_recharge ===
|
|
32
|
+
// 'in_flight'`, sleeping the server-provided `retry_after_seconds` (fallback
|
|
33
|
+
// 5s), up to maxRetries. Every other case — capped / no_pm / failed /
|
|
34
|
+
// disabled, or a `SPEND_CAP_EXCEEDED` hard ceiling — rethrows immediately:
|
|
35
|
+
// those need a human (top up, fix the card, raise the cap), not a retry.
|
|
36
|
+
async function withFundsRetry(call, opts = {}) {
|
|
37
|
+
const maxRetries = opts.maxRetries ?? 3;
|
|
38
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
39
|
+
let attempt = 0;
|
|
40
|
+
for (;;) {
|
|
41
|
+
try {
|
|
42
|
+
return await call();
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
if (attempt >= maxRetries || !isInsufficientFunds(err) || autoRechargeState(err) !== 'in_flight') {
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
attempt++;
|
|
49
|
+
const hinted = err.body?.retry_after_seconds;
|
|
50
|
+
const waitSec = typeof hinted === 'number' && hinted > 0 ? hinted : 5;
|
|
51
|
+
opts.onRetry?.(attempt, waitSec);
|
|
52
|
+
await sleep(waitSec * 1000);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
package/dist/hq.d.ts
CHANGED
|
@@ -138,6 +138,26 @@ export declare function setupPayment(apiKey: string): Promise<{
|
|
|
138
138
|
export declare function topUp(apiKey: string, amountDollars: number): Promise<{
|
|
139
139
|
new_balance_display: string;
|
|
140
140
|
}>;
|
|
141
|
+
export type RechargeStatus = 'succeeded' | 'failed' | 'capped' | 'no_pm' | 'pending';
|
|
142
|
+
export interface AutoRechargeConfig {
|
|
143
|
+
enabled: boolean;
|
|
144
|
+
threshold_cents: number | null;
|
|
145
|
+
amount_cents: number | null;
|
|
146
|
+
monthly_cap_cents: number | null;
|
|
147
|
+
month_to_date_recharged_cents: number;
|
|
148
|
+
has_payment_method: boolean;
|
|
149
|
+
last_recharge_status: RechargeStatus | null;
|
|
150
|
+
last_recharge_attempt_at: string | null;
|
|
151
|
+
}
|
|
152
|
+
export declare function getAutoRecharge(apiKey: string): Promise<AutoRechargeConfig>;
|
|
153
|
+
export interface SetAutoRechargeInput {
|
|
154
|
+
enabled: boolean;
|
|
155
|
+
threshold_cents?: number;
|
|
156
|
+
amount_cents?: number;
|
|
157
|
+
monthly_cap_cents?: number;
|
|
158
|
+
}
|
|
159
|
+
export declare function setAutoRecharge(apiKey: string, input: SetAutoRechargeInput): Promise<AutoRechargeConfig>;
|
|
160
|
+
export declare function disableAutoRecharge(apiKey: string): Promise<void>;
|
|
141
161
|
export type DoctorSeverity = 'ok' | 'warn' | 'crit';
|
|
142
162
|
export interface DoctorEntityRef {
|
|
143
163
|
slot: string;
|
|
@@ -152,11 +172,13 @@ export interface DoctorIssue {
|
|
|
152
172
|
category?: string;
|
|
153
173
|
message: string;
|
|
154
174
|
hint?: string;
|
|
175
|
+
operator_only?: boolean;
|
|
155
176
|
}
|
|
156
177
|
export interface DoctorSection {
|
|
157
178
|
name: string;
|
|
158
179
|
summary: string;
|
|
159
180
|
issues: DoctorIssue[];
|
|
181
|
+
resource_count?: number;
|
|
160
182
|
}
|
|
161
183
|
export interface DoctorReport {
|
|
162
184
|
org_id: string;
|
package/dist/hq.js
CHANGED
|
@@ -27,6 +27,9 @@ exports.getBillingHistory = getBillingHistory;
|
|
|
27
27
|
exports.getBillingUsage = getBillingUsage;
|
|
28
28
|
exports.setupPayment = setupPayment;
|
|
29
29
|
exports.topUp = topUp;
|
|
30
|
+
exports.getAutoRecharge = getAutoRecharge;
|
|
31
|
+
exports.setAutoRecharge = setAutoRecharge;
|
|
32
|
+
exports.disableAutoRecharge = disableAutoRecharge;
|
|
30
33
|
exports.getDoctor = getDoctor;
|
|
31
34
|
const client_1 = require("./client");
|
|
32
35
|
const config_1 = require("./config");
|
|
@@ -170,6 +173,21 @@ async function setupPayment(apiKey) {
|
|
|
170
173
|
async function topUp(apiKey, amountDollars) {
|
|
171
174
|
return (0, client_1.request)('POST', `${config_1.HQ_BASE}/hq/billing/topup`, apiKey, { amount_dollars: amountDollars });
|
|
172
175
|
}
|
|
176
|
+
async function getAutoRecharge(apiKey) {
|
|
177
|
+
return (0, client_1.request)('GET', `${config_1.HQ_BASE}/hq/billing/auto-recharge`, apiKey);
|
|
178
|
+
}
|
|
179
|
+
// Enable/update auto-recharge. The backend validates invariants and rejects
|
|
180
|
+
// with a 400 (`MISSING_FIELDS`, `AMOUNT_BELOW_FLOOR` [$5 floor],
|
|
181
|
+
// `AMOUNT_LT_THRESHOLD`, `CAP_LT_AMOUNT`, `NO_PAYMENT_METHOD`) — surfaced as a
|
|
182
|
+
// MyApiError. Returns the full config on success.
|
|
183
|
+
async function setAutoRecharge(apiKey, input) {
|
|
184
|
+
return (0, client_1.request)('PUT', `${config_1.HQ_BASE}/hq/billing/auto-recharge`, apiKey, input);
|
|
185
|
+
}
|
|
186
|
+
// Disable auto-recharge (the threshold/amount/cap are preserved for easy
|
|
187
|
+
// re-enable). Backend returns 204 No Content.
|
|
188
|
+
async function disableAutoRecharge(apiKey) {
|
|
189
|
+
return (0, client_1.request)('DELETE', `${config_1.HQ_BASE}/hq/billing/auto-recharge`, apiKey);
|
|
190
|
+
}
|
|
173
191
|
async function getDoctor(apiKey, orgId) {
|
|
174
192
|
return (0, client_1.request)('GET', `${config_1.HQ_BASE}/hq/orgs/${encodeURIComponent(orgId)}/doctor`, apiKey);
|
|
175
193
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
exports.task = exports.queue = exports.git = exports.container = exports.payments = exports.fn = exports.crm = exports.database = exports.llm = exports.audience = exports.company = exports.people = exports.url = exports.workflow = exports.webhook = exports.storage = exports.pixel = exports.image = exports.funnel = exports.email = exports.domain = exports.auth = exports.hq = void 0;
|
|
40
40
|
__exportStar(require("./types"), exports);
|
|
41
41
|
__exportStar(require("./client"), exports);
|
|
42
|
+
__exportStar(require("./funds"), exports);
|
|
42
43
|
// Note: config constants (STORAGE_BASE etc.) are NOT re-exported from the
|
|
43
44
|
// barrel. TypeScript compiles `export * from './config'` to a runtime
|
|
44
45
|
// `__exportStar` call that Node's cjs-module-lexer can't see through, so
|
package/dist/services.js
CHANGED
|
@@ -48,8 +48,8 @@ exports.SERVICES = [
|
|
|
48
48
|
},
|
|
49
49
|
// ── send ────────────────────────────────────────────────────────────
|
|
50
50
|
{
|
|
51
|
-
// Customer-facing email surface — mailboxes,
|
|
52
|
-
// warmup. Launched 2026-05-17: the backend lifted the pre-launch gate
|
|
51
|
+
// Customer-facing email surface — mailboxes, transactional send,
|
|
52
|
+
// templates, warmup. Launched 2026-05-17: the backend lifted the pre-launch gate
|
|
53
53
|
// (the [disabled, pre-launch] / 503 SERVICE_NOT_LAUNCHED state is gone).
|
|
54
54
|
// Sibling `my-email-verify-api` is the sync single-address verifier.
|
|
55
55
|
module: 'email',
|
package/package.json
CHANGED
package/src/auth.ts
CHANGED
|
@@ -20,9 +20,12 @@ export const EXPOSES: Exposes = [
|
|
|
20
20
|
'GET /auth/orgs/{org_id}/tenant',
|
|
21
21
|
'POST /auth/orgs/{org_id}/clients',
|
|
22
22
|
'GET /auth/orgs/{org_id}/clients',
|
|
23
|
+
'DELETE /auth/orgs/{org_id}/clients/{client_id}',
|
|
24
|
+
'POST /auth/orgs/{org_id}/clients/{client_id}/rotate',
|
|
23
25
|
'GET /auth/orgs/{org_id}/usage',
|
|
24
26
|
'POST /auth/orgs/{org_id}/domain',
|
|
25
27
|
'GET /auth/orgs/{org_id}/domain',
|
|
28
|
+
'POST /auth/orgs/{org_id}/domain/verify',
|
|
26
29
|
'DELETE /auth/orgs/{org_id}/domain',
|
|
27
30
|
];
|
|
28
31
|
|
|
@@ -70,13 +73,21 @@ export interface AuthUsage {
|
|
|
70
73
|
period: string; // 'YYYY-MM'
|
|
71
74
|
active_users: number;
|
|
72
75
|
price_cents_each: number;
|
|
76
|
+
as_of?: string; // RFC3339 freshness marker — MAU is aggregated, not real-time
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
// A custom auth domain (e.g. auth.acme.com) for the tenant's hosted login +
|
|
79
|
+
// A custom auth domain (e.g. auth.acme.com) for the tenant's hosted login +
|
|
80
|
+
// issuer. Lifecycle: awaiting_verification (publish the TXT challenge, then
|
|
81
|
+
// `verify`) → pending (publish the A record, TLS provisions) → active.
|
|
76
82
|
export interface AuthDomain {
|
|
77
83
|
domain: string;
|
|
78
|
-
status: 'pending' | 'active';
|
|
79
|
-
|
|
84
|
+
status: 'awaiting_verification' | 'pending' | 'active';
|
|
85
|
+
// Present while `awaiting_verification` — the ownership-challenge TXT record
|
|
86
|
+
// to publish, then call verifyDomain().
|
|
87
|
+
verification?: { type: string; name: string; value: string };
|
|
88
|
+
// Present once `pending`/`active` — the A record to publish (withheld until
|
|
89
|
+
// ownership is verified).
|
|
90
|
+
dns?: { type: string; name: string; value: string };
|
|
80
91
|
next?: string;
|
|
81
92
|
issuer?: string; // present once active
|
|
82
93
|
login_url?: string; // present once active
|
|
@@ -107,6 +118,19 @@ export async function listClients(apiKey: string, orgId: string): Promise<ListCl
|
|
|
107
118
|
return request('GET', `${BASE_URL}/auth/orgs/${encodeURIComponent(orgId)}/clients`, apiKey);
|
|
108
119
|
}
|
|
109
120
|
|
|
121
|
+
// Delete (revoke) an OIDC client. Irreversible — the client_id stops
|
|
122
|
+
// authenticating immediately.
|
|
123
|
+
export async function deleteClient(apiKey: string, orgId: string, clientId: string): Promise<{ client_id: string; deleted: boolean }> {
|
|
124
|
+
return request('DELETE', `${BASE_URL}/auth/orgs/${encodeURIComponent(orgId)}/clients/${encodeURIComponent(clientId)}`, apiKey);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Rotate a confidential ('web') client's secret. Returns the NEW secret exactly
|
|
128
|
+
// once; the previous secret stops working immediately. (SPA clients have no
|
|
129
|
+
// secret — rotating one is rejected by the backend.)
|
|
130
|
+
export async function rotateClient(apiKey: string, orgId: string, clientId: string): Promise<AuthClient> {
|
|
131
|
+
return request('POST', `${BASE_URL}/auth/orgs/${encodeURIComponent(orgId)}/clients/${encodeURIComponent(clientId)}/rotate`, apiKey);
|
|
132
|
+
}
|
|
133
|
+
|
|
110
134
|
// Monthly-active-user usage for the current period (auth is billed per MAU).
|
|
111
135
|
export async function getUsage(apiKey: string, orgId: string): Promise<AuthUsage> {
|
|
112
136
|
return request('GET', `${BASE_URL}/auth/orgs/${encodeURIComponent(orgId)}/usage`, apiKey);
|
|
@@ -124,6 +148,14 @@ export async function getDomain(apiKey: string, orgId: string): Promise<AuthDoma
|
|
|
124
148
|
return request('GET', `${BASE_URL}/auth/orgs/${encodeURIComponent(orgId)}/domain`, apiKey);
|
|
125
149
|
}
|
|
126
150
|
|
|
151
|
+
// Verify ownership of the org's custom auth domain by checking the published
|
|
152
|
+
// TXT challenge. On success the domain advances awaiting_verification → pending
|
|
153
|
+
// (and the response carries the A record). Rejects with 422 DOMAIN_NOT_VERIFIED
|
|
154
|
+
// if the TXT record isn't found yet. Idempotent once already pending/active.
|
|
155
|
+
export async function verifyDomain(apiKey: string, orgId: string): Promise<AuthDomain> {
|
|
156
|
+
return request('POST', `${BASE_URL}/auth/orgs/${encodeURIComponent(orgId)}/domain/verify`, apiKey);
|
|
157
|
+
}
|
|
158
|
+
|
|
127
159
|
export async function deleteDomain(apiKey: string, orgId: string, domain: string): Promise<{ domain: string; status: string }> {
|
|
128
160
|
return request('DELETE', `${BASE_URL}/auth/orgs/${encodeURIComponent(orgId)}/domain`, apiKey, { domain });
|
|
129
161
|
}
|
package/src/funds.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { MyApiError } from './client';
|
|
2
|
+
|
|
3
|
+
// The auto-recharge state the backend carries on an `INSUFFICIENT_FUNDS` 402.
|
|
4
|
+
// `in_flight` = a refill was triggered and is on its way (retry); everything
|
|
5
|
+
// else needs a human action, not a retry.
|
|
6
|
+
export type AutoRechargeState = 'in_flight' | 'disabled' | 'capped' | 'no_pm' | 'failed';
|
|
7
|
+
|
|
8
|
+
// Wallet empty. Matches the unified `INSUFFICIENT_FUNDS` and the legacy
|
|
9
|
+
// `INSUFFICIENT_BALANCE` (still emitted by imagegen's free-tier path until the
|
|
10
|
+
// backend finishes the split — tolerate both).
|
|
11
|
+
export function isInsufficientFunds(err: unknown): err is MyApiError {
|
|
12
|
+
return err instanceof MyApiError &&
|
|
13
|
+
(err.code === 'INSUFFICIENT_FUNDS' || err.code === 'INSUFFICIENT_BALANCE');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Hard account spend ceiling hit (distinct from an empty wallet). Always needs
|
|
17
|
+
// a human — raising/clearing the cap — never a retry or top-up.
|
|
18
|
+
export function isSpendCapExceeded(err: unknown): err is MyApiError {
|
|
19
|
+
return err instanceof MyApiError && err.code === 'SPEND_CAP_EXCEEDED';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// The auto_recharge state on an INSUFFICIENT_FUNDS error, if the backend set it.
|
|
23
|
+
export function autoRechargeState(err: unknown): AutoRechargeState | undefined {
|
|
24
|
+
if (!(err instanceof MyApiError)) return undefined;
|
|
25
|
+
const s = err.body?.auto_recharge;
|
|
26
|
+
return typeof s === 'string' ? (s as AutoRechargeState) : undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface FundsRetryOptions {
|
|
30
|
+
// Max poll-and-retry attempts on an in-flight refill (default 3).
|
|
31
|
+
maxRetries?: number;
|
|
32
|
+
// Injectable sleep (tests pass a no-op; default is real setTimeout).
|
|
33
|
+
sleep?: (ms: number) => Promise<void>;
|
|
34
|
+
// Called before each wait — e.g. to log "balance low, refill in flight…".
|
|
35
|
+
onRetry?: (attempt: number, waitSeconds: number) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const defaultSleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
|
|
39
|
+
|
|
40
|
+
// Wrap a billable call so an empty wallet with an auto-recharge IN FLIGHT is
|
|
41
|
+
// polled-and-retried instead of failing — the whole point of the 402 split,
|
|
42
|
+
// so an autonomous agent doesn't escalate to a human for a refill that's
|
|
43
|
+
// already coming. Retries ONLY on `INSUFFICIENT_FUNDS` + `auto_recharge ===
|
|
44
|
+
// 'in_flight'`, sleeping the server-provided `retry_after_seconds` (fallback
|
|
45
|
+
// 5s), up to maxRetries. Every other case — capped / no_pm / failed /
|
|
46
|
+
// disabled, or a `SPEND_CAP_EXCEEDED` hard ceiling — rethrows immediately:
|
|
47
|
+
// those need a human (top up, fix the card, raise the cap), not a retry.
|
|
48
|
+
export async function withFundsRetry<T>(call: () => Promise<T>, opts: FundsRetryOptions = {}): Promise<T> {
|
|
49
|
+
const maxRetries = opts.maxRetries ?? 3;
|
|
50
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
51
|
+
let attempt = 0;
|
|
52
|
+
for (;;) {
|
|
53
|
+
try {
|
|
54
|
+
return await call();
|
|
55
|
+
} catch (err) {
|
|
56
|
+
if (attempt >= maxRetries || !isInsufficientFunds(err) || autoRechargeState(err) !== 'in_flight') {
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
attempt++;
|
|
60
|
+
const hinted = (err as MyApiError).body?.retry_after_seconds;
|
|
61
|
+
const waitSec = typeof hinted === 'number' && hinted > 0 ? hinted : 5;
|
|
62
|
+
opts.onRetry?.(attempt, waitSec);
|
|
63
|
+
await sleep(waitSec * 1000);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/hq.ts
CHANGED
|
@@ -281,6 +281,51 @@ export async function topUp(apiKey: string, amountDollars: number): Promise<{ ne
|
|
|
281
281
|
return request('POST', `${BASE_URL}/hq/billing/topup`, apiKey, { amount_dollars: amountDollars });
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
+
// ── Auto-recharge ──────────────────────────────────────────────────────
|
|
285
|
+
// Keeps the prepaid wallet funded without a human in the loop: when the
|
|
286
|
+
// balance drops below `threshold_cents`, the backend charges the saved card
|
|
287
|
+
// `amount_cents` off-session, bounded by `monthly_cap_cents`. Opt-in, off by
|
|
288
|
+
// default; enabling requires a saved payment method.
|
|
289
|
+
|
|
290
|
+
export type RechargeStatus = 'succeeded' | 'failed' | 'capped' | 'no_pm' | 'pending';
|
|
291
|
+
|
|
292
|
+
export interface AutoRechargeConfig {
|
|
293
|
+
enabled: boolean;
|
|
294
|
+
threshold_cents: number | null;
|
|
295
|
+
amount_cents: number | null;
|
|
296
|
+
monthly_cap_cents: number | null;
|
|
297
|
+
// Auto-recharge dollars charged this calendar month (vs the cap).
|
|
298
|
+
month_to_date_recharged_cents: number;
|
|
299
|
+
has_payment_method: boolean;
|
|
300
|
+
last_recharge_status: RechargeStatus | null;
|
|
301
|
+
last_recharge_attempt_at: string | null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export async function getAutoRecharge(apiKey: string): Promise<AutoRechargeConfig> {
|
|
305
|
+
return request('GET', `${BASE_URL}/hq/billing/auto-recharge`, apiKey);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export interface SetAutoRechargeInput {
|
|
309
|
+
enabled: boolean;
|
|
310
|
+
threshold_cents?: number;
|
|
311
|
+
amount_cents?: number;
|
|
312
|
+
monthly_cap_cents?: number;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Enable/update auto-recharge. The backend validates invariants and rejects
|
|
316
|
+
// with a 400 (`MISSING_FIELDS`, `AMOUNT_BELOW_FLOOR` [$5 floor],
|
|
317
|
+
// `AMOUNT_LT_THRESHOLD`, `CAP_LT_AMOUNT`, `NO_PAYMENT_METHOD`) — surfaced as a
|
|
318
|
+
// MyApiError. Returns the full config on success.
|
|
319
|
+
export async function setAutoRecharge(apiKey: string, input: SetAutoRechargeInput): Promise<AutoRechargeConfig> {
|
|
320
|
+
return request('PUT', `${BASE_URL}/hq/billing/auto-recharge`, apiKey, input);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Disable auto-recharge (the threshold/amount/cap are preserved for easy
|
|
324
|
+
// re-enable). Backend returns 204 No Content.
|
|
325
|
+
export async function disableAutoRecharge(apiKey: string): Promise<void> {
|
|
326
|
+
return request('DELETE', `${BASE_URL}/hq/billing/auto-recharge`, apiKey);
|
|
327
|
+
}
|
|
328
|
+
|
|
284
329
|
// ── Org doctor ─────────────────────────────────────────────────────────
|
|
285
330
|
// Aggregated consistency report across slots. The endpoint resolves the
|
|
286
331
|
// reported org from the API key's binding, so the `{org_id}` path param
|
|
@@ -310,12 +355,26 @@ export interface DoctorIssue {
|
|
|
310
355
|
category?: string;
|
|
311
356
|
message: string;
|
|
312
357
|
hint?: string;
|
|
358
|
+
// True when the issue is platform-side and NOT actionable by the customer
|
|
359
|
+
// (e.g. a degraded internal dependency the MyAPI team owns). The backend
|
|
360
|
+
// sets this together with `category: 'internal'` and a customer-appropriate
|
|
361
|
+
// message; the operator detail is routed to an internal sink, not here.
|
|
362
|
+
// Consumers should surface these for transparency but must NOT count them
|
|
363
|
+
// as customer-actionable failures (they don't fail `doctor`'s exit code).
|
|
364
|
+
operator_only?: boolean;
|
|
313
365
|
}
|
|
314
366
|
|
|
315
367
|
export interface DoctorSection {
|
|
316
368
|
name: string;
|
|
317
369
|
summary: string;
|
|
318
370
|
issues: DoctorIssue[];
|
|
371
|
+
// Authoritative count of resources of this section's kind in the org,
|
|
372
|
+
// independent of how many issues were emitted. Lets a consumer tell
|
|
373
|
+
// "zero resources" apart from "resources present, all healthy" without
|
|
374
|
+
// inferring it from `issues.length` (which breaks the moment the backend
|
|
375
|
+
// stops emitting an `ok` row per healthy resource). Optional: older
|
|
376
|
+
// backends omit it, and consumers must fall back to the issue-count proxy.
|
|
377
|
+
resource_count?: number;
|
|
319
378
|
}
|
|
320
379
|
|
|
321
380
|
export interface DoctorReport {
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from './types';
|
|
2
2
|
export * from './client';
|
|
3
|
+
export * from './funds';
|
|
3
4
|
// Note: config constants (STORAGE_BASE etc.) are NOT re-exported from the
|
|
4
5
|
// barrel. TypeScript compiles `export * from './config'` to a runtime
|
|
5
6
|
// `__exportStar` call that Node's cjs-module-lexer can't see through, so
|
package/src/services.ts
CHANGED
|
@@ -75,8 +75,8 @@ export const SERVICES: readonly ServiceMeta[] = [
|
|
|
75
75
|
|
|
76
76
|
// ── send ────────────────────────────────────────────────────────────
|
|
77
77
|
{
|
|
78
|
-
// Customer-facing email surface — mailboxes,
|
|
79
|
-
// warmup. Launched 2026-05-17: the backend lifted the pre-launch gate
|
|
78
|
+
// Customer-facing email surface — mailboxes, transactional send,
|
|
79
|
+
// templates, warmup. Launched 2026-05-17: the backend lifted the pre-launch gate
|
|
80
80
|
// (the [disabled, pre-launch] / 503 SERVICE_NOT_LAUNCHED state is gone).
|
|
81
81
|
// Sibling `my-email-verify-api` is the sync single-address verifier.
|
|
82
82
|
module: 'email',
|