@myapihq/sdk 1.3.13 → 2.1.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 +58 -0
- package/dist/auth.js +76 -0
- package/dist/client.d.ts +6 -0
- package/dist/client.js +16 -3
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -1
- package/dist/container.d.ts +8 -0
- package/dist/container.js +26 -2
- package/dist/crm.d.ts +1 -1
- package/dist/crm.js +4 -1
- package/dist/database.js +4 -1
- package/dist/domain.d.ts +1 -1
- package/dist/email.d.ts +0 -74
- package/dist/email.js +1 -70
- package/dist/function.d.ts +4 -0
- package/dist/function.js +8 -0
- package/dist/funds.d.ts +11 -0
- package/dist/funds.js +55 -0
- package/dist/funnel.d.ts +1 -1
- package/dist/hq.d.ts +22 -0
- package/dist/hq.js +18 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -1
- package/dist/payments.d.ts +1 -0
- package/dist/payments.js +1 -1
- package/dist/queue.d.ts +1 -0
- package/dist/queue.js +5 -0
- package/dist/services.js +17 -8
- package/dist/storage.d.ts +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/webhook.d.ts +4 -0
- package/dist/workflow.d.ts +11 -1
- package/dist/workflow.js +12 -2
- package/package.json +1 -1
- package/src/auth.ts +129 -0
- package/src/client.ts +36 -4
- package/src/config.ts +1 -0
- package/src/container.ts +53 -3
- package/src/crm.ts +7 -3
- package/src/database.ts +5 -2
- package/src/domain.ts +1 -1
- package/src/email.ts +1 -78
- package/src/function.ts +12 -0
- package/src/funds.ts +66 -0
- package/src/funnel.ts +1 -1
- package/src/hq.ts +59 -0
- package/src/index.ts +2 -0
- package/src/payments.ts +2 -1
- package/src/queue.ts +5 -0
- package/src/services.ts +17 -8
- package/src/storage.ts +2 -0
- package/src/types.ts +3 -1
- package/src/webhook.ts +10 -0
- package/src/workflow.ts +34 -3
package/src/domain.ts
CHANGED
|
@@ -211,7 +211,7 @@ export async function deleteDnsRecord(apiKey: string, orgId: string, domain: str
|
|
|
211
211
|
export interface EmailInfraResponse {
|
|
212
212
|
domain: string;
|
|
213
213
|
email_infra: 'pending' | 'ready' | 'error';
|
|
214
|
-
|
|
214
|
+
subdomain: string;
|
|
215
215
|
next_step?: string;
|
|
216
216
|
}
|
|
217
217
|
|
package/src/email.ts
CHANGED
|
@@ -31,18 +31,6 @@ export const EXPOSES: Exposes = [
|
|
|
31
31
|
'POST /email/orgs/{org_id}/templates/{template_id}/send-test',
|
|
32
32
|
'DELETE /email/orgs/{org_id}/templates/{template_id}',
|
|
33
33
|
'GET /email/templates/{template_id}/preview',
|
|
34
|
-
// Campaigns
|
|
35
|
-
'POST /email/orgs/{org_id}/campaigns',
|
|
36
|
-
'GET /email/orgs/{org_id}/campaigns',
|
|
37
|
-
'GET /email/orgs/{org_id}/campaigns/{campaign_id}',
|
|
38
|
-
'PATCH /email/orgs/{org_id}/campaigns/{campaign_id}',
|
|
39
|
-
'POST /email/orgs/{org_id}/campaigns/{campaign_id}/contacts/upload-list',
|
|
40
|
-
'POST /email/orgs/{org_id}/campaigns/{campaign_id}/contacts/upload-file',
|
|
41
|
-
'GET /email/orgs/{org_id}/campaigns/{campaign_id}/contacts/status',
|
|
42
|
-
'POST /email/orgs/{org_id}/campaigns/{campaign_id}/start',
|
|
43
|
-
'POST /email/orgs/{org_id}/campaigns/{campaign_id}/pause',
|
|
44
|
-
'POST /email/orgs/{org_id}/campaigns/{campaign_id}/resume',
|
|
45
|
-
'GET /email/orgs/{org_id}/campaigns/{campaign_id}/stats',
|
|
46
34
|
// Verify
|
|
47
35
|
'POST /email/orgs/{org_id}/verify',
|
|
48
36
|
'POST /email/orgs/{org_id}/verify-bulk',
|
|
@@ -51,14 +39,6 @@ export const EXPOSES: Exposes = [
|
|
|
51
39
|
|
|
52
40
|
export interface EmailMessage { message_id: string; from: string; subject: string; body?: string; received_at: string }
|
|
53
41
|
export interface EmailTemplate { id: string; name: string; subject: string; preview_url: string; created_at: string; updated_at: string }
|
|
54
|
-
export interface WarmupConfig { enabled: boolean; start: number; increment: number; max: number; }
|
|
55
|
-
export interface Campaign { id: string; name: string; status: string; template_id: string; from_address: string; per_day_limit: number; warmup_config?: WarmupConfig; created_at: string }
|
|
56
|
-
export interface CampaignStats {
|
|
57
|
-
campaign: Campaign;
|
|
58
|
-
validation: { total: number; valid: number; invalid: number; pending: number; list_status: string };
|
|
59
|
-
dispatch: { sent: number; remaining: number; sent_this_hour: number; hour_limit: number; last_dispatch_at: string };
|
|
60
|
-
engagement: { sent: number; opens: number; clicks: number; page_visits: number };
|
|
61
|
-
}
|
|
62
42
|
|
|
63
43
|
// ── Email verification ──────────────────────────────────────────────────────
|
|
64
44
|
//
|
|
@@ -138,8 +118,7 @@ export async function createMailbox(apiKey: string, domain: string, username: st
|
|
|
138
118
|
}
|
|
139
119
|
|
|
140
120
|
// Backend (2026-06-06): idempotent — 204 whether the mailbox existed or not.
|
|
141
|
-
//
|
|
142
|
-
// this address; pause/delete the campaign first.
|
|
121
|
+
// May return 409 MAILBOX_IN_USE if the address is still referenced server-side.
|
|
143
122
|
export async function deleteMailbox(apiKey: string, address: string): Promise<void> {
|
|
144
123
|
return request('DELETE', `${BASE_URL}/email/mailboxes/${encodeURIComponent(address)}`, apiKey);
|
|
145
124
|
}
|
|
@@ -264,59 +243,3 @@ export async function listTemplates(apiKey: string, orgId: string): Promise<Emai
|
|
|
264
243
|
export async function deleteTemplate(apiKey: string, orgId: string, templateId: string): Promise<void> {
|
|
265
244
|
return request('DELETE', `${BASE_URL}/email/orgs/${encodeURIComponent(orgId)}/templates/${encodeURIComponent(templateId)}`, apiKey);
|
|
266
245
|
}
|
|
267
|
-
|
|
268
|
-
// ── Campaigns (org-scoped) ───────────────────────────────────────────────────
|
|
269
|
-
|
|
270
|
-
export async function createCampaign(apiKey: string, orgId: string, payload: { name: string; template_id: string; from_address: string; per_day_limit?: number; warmup_config?: WarmupConfig | null; }): Promise<Campaign> {
|
|
271
|
-
return request('POST', `${BASE_URL}/email/orgs/${encodeURIComponent(orgId)}/campaigns`, apiKey, payload);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
export async function uploadContactList(apiKey: string, orgId: string, campaignId: string, emails: string[]): Promise<{ list_id: string; total: number; status: string }> {
|
|
275
|
-
return request('POST', `${BASE_URL}/email/orgs/${encodeURIComponent(orgId)}/campaigns/${encodeURIComponent(campaignId)}/contacts/upload-list`, apiKey, { emails });
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
export async function uploadContactsFile(apiKey: string, orgId: string, campaignId: string, file: Blob | ArrayBuffer | Uint8Array, filename: string = 'contacts.csv'): Promise<{ list_id: string; total: number; status: string }> {
|
|
279
|
-
const form = new FormData();
|
|
280
|
-
const blob = file instanceof Blob ? file : new Blob([file as BlobPart]);
|
|
281
|
-
form.append('file', blob, filename);
|
|
282
|
-
const res = await fetch(`${BASE_URL}/email/orgs/${encodeURIComponent(orgId)}/campaigns/${encodeURIComponent(campaignId)}/contacts/upload-file`, {
|
|
283
|
-
method: 'POST',
|
|
284
|
-
headers: { Authorization: `Bearer ${apiKey}` },
|
|
285
|
-
body: form,
|
|
286
|
-
});
|
|
287
|
-
const json = await res.json();
|
|
288
|
-
if (!res.ok || !json.success) throw new Error(json.error?.code || json.error || 'upload_failed');
|
|
289
|
-
return json.data;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
export async function getContactUploadStatus(apiKey: string, orgId: string, campaignId: string): Promise<{ upload_status: 'ready' | 'validating' | 'failed'; total: number; valid_count: number; invalid_count: number }> {
|
|
293
|
-
return request('GET', `${BASE_URL}/email/orgs/${encodeURIComponent(orgId)}/campaigns/${encodeURIComponent(campaignId)}/contacts/status`, apiKey);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
export async function startCampaign(apiKey: string, orgId: string, campaignId: string): Promise<{ status: string }> {
|
|
297
|
-
return request('POST', `${BASE_URL}/email/orgs/${encodeURIComponent(orgId)}/campaigns/${encodeURIComponent(campaignId)}/start`, apiKey);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
export async function pauseCampaign(apiKey: string, orgId: string, campaignId: string): Promise<void> {
|
|
301
|
-
return request('POST', `${BASE_URL}/email/orgs/${encodeURIComponent(orgId)}/campaigns/${encodeURIComponent(campaignId)}/pause`, apiKey);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
export async function resumeCampaign(apiKey: string, orgId: string, campaignId: string): Promise<void> {
|
|
305
|
-
return request('POST', `${BASE_URL}/email/orgs/${encodeURIComponent(orgId)}/campaigns/${encodeURIComponent(campaignId)}/resume`, apiKey);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
export async function getCampaignStats(apiKey: string, orgId: string, campaignId: string): Promise<CampaignStats> {
|
|
309
|
-
return request('GET', `${BASE_URL}/email/orgs/${encodeURIComponent(orgId)}/campaigns/${encodeURIComponent(campaignId)}/stats`, apiKey);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
export async function listCampaigns(apiKey: string, orgId: string): Promise<Campaign[]> {
|
|
313
|
-
return request('GET', `${BASE_URL}/email/orgs/${encodeURIComponent(orgId)}/campaigns`, apiKey);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
export async function getCampaign(apiKey: string, orgId: string, campaignId: string): Promise<Campaign> {
|
|
317
|
-
return request('GET', `${BASE_URL}/email/orgs/${encodeURIComponent(orgId)}/campaigns/${encodeURIComponent(campaignId)}`, apiKey);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
export async function updateCampaign(apiKey: string, orgId: string, campaignId: string, payload: { name?: string; per_day_limit?: number }): Promise<Campaign> {
|
|
321
|
-
return request('PATCH', `${BASE_URL}/email/orgs/${encodeURIComponent(orgId)}/campaigns/${encodeURIComponent(campaignId)}`, apiKey, payload);
|
|
322
|
-
}
|
package/src/function.ts
CHANGED
|
@@ -35,6 +35,10 @@ export interface CreatePayload {
|
|
|
35
35
|
name: string; // required; ^[a-z0-9][a-z0-9-]{0,49}$
|
|
36
36
|
trigger_type?: TriggerType; // optional, defaults to 'http' server-side
|
|
37
37
|
cron_schedule?: string; // required if trigger_type === 'cron'
|
|
38
|
+
// Optional slot allow-list narrowing the minted function key's grants
|
|
39
|
+
// (backend crud.go: CreateFunction `scopes`). Empty/omitted inherits the
|
|
40
|
+
// deploying caller's grants (legacy). Grants can never exceed the caller.
|
|
41
|
+
scopes?: string[];
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
// POST response envelope includes the scoped API key — returned ONCE. The
|
|
@@ -122,6 +126,14 @@ export async function setFunctionEnv(apiKey: string, orgId: string, fnId: string
|
|
|
122
126
|
return request('POST', `${BASE_URL}/function/orgs/${encodeURIComponent(orgId)}/functions/${encodeURIComponent(fnId)}/env`, apiKey, { name, value });
|
|
123
127
|
}
|
|
124
128
|
|
|
129
|
+
// setFunctionEnvBulk writes multiple Worker Secrets in one call. The backend
|
|
130
|
+
// (env.go) loops, calling Cloudflare once per secret, and returns `{set:N}`.
|
|
131
|
+
// The single-secret `setFunctionEnv` above stays the canonical one-off path;
|
|
132
|
+
// this is the bulk variant. The function must already be deployed.
|
|
133
|
+
export async function setFunctionEnvBulk(apiKey: string, orgId: string, fnId: string, env: Record<string, string>): Promise<{ set: number }> {
|
|
134
|
+
return request('POST', `${BASE_URL}/function/orgs/${encodeURIComponent(orgId)}/functions/${encodeURIComponent(fnId)}/env`, apiKey, { env });
|
|
135
|
+
}
|
|
136
|
+
|
|
125
137
|
// listFunctionRuns returns recent invocation records, most recent first
|
|
126
138
|
// (Story 4). Capped at 100 server-side.
|
|
127
139
|
export async function listFunctionRuns(apiKey: string, orgId: string, fnId: string): Promise<FunctionRun[]> {
|
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/funnel.ts
CHANGED
|
@@ -62,7 +62,7 @@ export interface VerifyResult {
|
|
|
62
62
|
pages?: Array<{ slug: string; tests: VerifyTest[] }>;
|
|
63
63
|
tests?: VerifyTest[];
|
|
64
64
|
}
|
|
65
|
-
export interface VerifyTest { type: 'link' | 'webhook'; url: string; passed: boolean; details: string }
|
|
65
|
+
export interface VerifyTest { type: 'link' | 'image' | 'webhook'; url: string; passed: boolean; details: string }
|
|
66
66
|
|
|
67
67
|
export async function createFunnel(apiKey: string, orgId: string, opts?: { name?: string }): Promise<CreateFunnelResponse> {
|
|
68
68
|
// v2: accept optional name for N-funnels-per-org. Backend rejects names
|
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
|
|
@@ -7,6 +8,7 @@ export * from './client';
|
|
|
7
8
|
// CJS-compiled SDK. If you need a base URL in a downstream package, mirror
|
|
8
9
|
// the constant locally — it's one line and the comment makes it obvious.
|
|
9
10
|
export * as hq from './hq';
|
|
11
|
+
export * as auth from './auth';
|
|
10
12
|
export * as domain from './domain';
|
|
11
13
|
export * as email from './email';
|
|
12
14
|
export * as funnel from './funnel';
|
package/src/payments.ts
CHANGED
|
@@ -19,6 +19,7 @@ export const EXPOSES: Exposes = [
|
|
|
19
19
|
// Connection status — the org's Stripe link. T0 takes no platform fee, so
|
|
20
20
|
// `application_fee_bps` is 0.
|
|
21
21
|
export interface ConnectStatus {
|
|
22
|
+
connected?: boolean;
|
|
22
23
|
tier: string;
|
|
23
24
|
stripe_account_id: string;
|
|
24
25
|
onboarding_status: string;
|
|
@@ -71,7 +72,7 @@ export async function connect(apiKey: string, orgId: string, stripeSecretKey: st
|
|
|
71
72
|
});
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
// getConnect reads the org's connection status.
|
|
75
|
+
// getConnect reads the org's connection status. Returns { connected: false } when no Stripe is linked (no longer a 404).
|
|
75
76
|
// when the org has not connected Stripe.
|
|
76
77
|
export async function getConnect(apiKey: string, orgId: string): Promise<ConnectStatus> {
|
|
77
78
|
return request('GET', `${BASE_URL}/payments/orgs/${encodeURIComponent(orgId)}/connect`, apiKey);
|
package/src/queue.ts
CHANGED
|
@@ -10,6 +10,7 @@ export const EXPOSES: Exposes = [
|
|
|
10
10
|
'POST /queue/orgs/{org_id}/queues',
|
|
11
11
|
'GET /queue/orgs/{org_id}/queues',
|
|
12
12
|
'GET /queue/orgs/{org_id}/queues/{name}',
|
|
13
|
+
'DELETE /queue/orgs/{org_id}/queues/{name}',
|
|
13
14
|
'POST /queue/orgs/{org_id}/queues/{name}/jobs',
|
|
14
15
|
'GET /queue/orgs/{org_id}/queues/{name}/jobs',
|
|
15
16
|
'GET /queue/orgs/{org_id}/jobs/{id}',
|
|
@@ -84,6 +85,10 @@ export async function getQueue(apiKey: string, orgId: string, name: string): Pro
|
|
|
84
85
|
return request('GET', `${queuesBase(orgId)}/${encodeURIComponent(name)}`, apiKey);
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
export async function deleteQueue(apiKey: string, orgId: string, name: string): Promise<void> {
|
|
89
|
+
return request('DELETE', `${queuesBase(orgId)}/${encodeURIComponent(name)}`, apiKey);
|
|
90
|
+
}
|
|
91
|
+
|
|
87
92
|
// enqueueJob is idempotent on (queue, dedup_key). A job with unsucceeded
|
|
88
93
|
// depends_on starts blocked.
|
|
89
94
|
export async function enqueueJob(apiKey: string, orgId: string, name: string, opts: EnqueueOptions = {}): Promise<Job> {
|
package/src/services.ts
CHANGED
|
@@ -54,6 +54,15 @@ export const SERVICES: readonly ServiceMeta[] = [
|
|
|
54
54
|
status: 'ga',
|
|
55
55
|
keywords: k('auth', 'identity', 'billing', 'organization'),
|
|
56
56
|
},
|
|
57
|
+
{
|
|
58
|
+
module: 'auth',
|
|
59
|
+
skill: 'my-auth-api',
|
|
60
|
+
domain: 'myauthapi.com',
|
|
61
|
+
description: 'Managed OIDC identity provider for the end users of apps built on MyAPI. Per-org auth tenant + OIDC clients, RS256/JWKS, hosted login, managed Google sign-in. A Kinde alternative.',
|
|
62
|
+
category: 'identity',
|
|
63
|
+
status: 'ga',
|
|
64
|
+
keywords: k('auth', 'oidc', 'oauth', 'login', 'identity', 'sso'),
|
|
65
|
+
},
|
|
57
66
|
{
|
|
58
67
|
module: 'domain',
|
|
59
68
|
skill: 'my-domain-api',
|
|
@@ -66,23 +75,23 @@ export const SERVICES: readonly ServiceMeta[] = [
|
|
|
66
75
|
|
|
67
76
|
// ── send ────────────────────────────────────────────────────────────
|
|
68
77
|
{
|
|
69
|
-
// Customer-facing email surface — mailboxes,
|
|
70
|
-
// 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
|
|
71
80
|
// (the [disabled, pre-launch] / 503 SERVICE_NOT_LAUNCHED state is gone).
|
|
72
81
|
// Sibling `my-email-verify-api` is the sync single-address verifier.
|
|
73
82
|
module: 'email',
|
|
74
83
|
skill: 'my-email-api',
|
|
75
84
|
domain: 'myemailapi.com',
|
|
76
|
-
description: 'Send transactional and bulk email from your own domain. Mailboxes, AI templates,
|
|
85
|
+
description: 'Send transactional and bulk email from your own domain. Mailboxes, AI templates, and warmup.',
|
|
77
86
|
category: 'send',
|
|
78
87
|
status: 'ga',
|
|
79
|
-
keywords: k('email', 'transactional', '
|
|
88
|
+
keywords: k('email', 'transactional', 'smtp', 'mailbox'),
|
|
80
89
|
},
|
|
81
90
|
{
|
|
82
91
|
module: 'email',
|
|
83
92
|
skill: 'my-email-verify-api',
|
|
84
93
|
domain: 'myemailapi.com',
|
|
85
|
-
description: 'Sync single-address email verification — syntax + DNS + Microsoft probe. Pre-send quality gate for
|
|
94
|
+
description: 'Sync single-address email verification — syntax + DNS + Microsoft probe. Pre-send quality gate for outbound.',
|
|
86
95
|
category: 'send',
|
|
87
96
|
status: 'ga',
|
|
88
97
|
keywords: k('email', 'verification', 'deliverability', 'syntax', 'dns'),
|
|
@@ -206,10 +215,10 @@ export const SERVICES: readonly ServiceMeta[] = [
|
|
|
206
215
|
module: 'llm',
|
|
207
216
|
skill: 'my-llm-api',
|
|
208
217
|
domain: 'myllmapi.com',
|
|
209
|
-
description: '
|
|
218
|
+
description: 'Self-hosted open-source LLM: raw chat completions (you pick the model) + objective verbs (classify/extract/summarize/draft). Billed in cents per 1M tokens from your balance. Use in workflow steps, not for your own reasoning.',
|
|
210
219
|
category: 'compute',
|
|
211
220
|
status: 'ga',
|
|
212
|
-
keywords: k('llm', 'completion', '
|
|
221
|
+
keywords: k('llm', 'completion', 'qwen', 'classify'),
|
|
213
222
|
},
|
|
214
223
|
{
|
|
215
224
|
// CLI top-level command is `fn`; SDK namespace is `fn`; skill directory is
|
|
@@ -267,7 +276,7 @@ export const SERVICES: readonly ServiceMeta[] = [
|
|
|
267
276
|
domain: 'mygitapi.com',
|
|
268
277
|
description: 'Hosted git repositories over HTTP — create repos, commit files, manage branches and tags, read trees, blobs, history, and diffs. No local clone required.',
|
|
269
278
|
category: 'store',
|
|
270
|
-
status: '
|
|
279
|
+
status: 'ga',
|
|
271
280
|
keywords: k('git', 'repository', 'version-control', 'commit', 'scm'),
|
|
272
281
|
},
|
|
273
282
|
|
package/src/storage.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -2,7 +2,9 @@ export interface ApiResponse<T> {
|
|
|
2
2
|
success: boolean;
|
|
3
3
|
data: T | null;
|
|
4
4
|
error: string | null;
|
|
5
|
-
|
|
5
|
+
// Pagination fields (next_cursor / has_more / limit) appear on keyset-paginated
|
|
6
|
+
// list endpoints (backend `envelope.WrapPage`) — read via `requestPage`.
|
|
7
|
+
meta: { request_id: string; latency_ms: number; service: string; version: string; next_cursor?: string; has_more?: boolean; limit?: number };
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
export interface PaginatedResponse<T> {
|
package/src/webhook.ts
CHANGED
|
@@ -21,6 +21,9 @@ export interface WebhookEndpoint {
|
|
|
21
21
|
// into CRM as a contact + webhook_received event on each delivery.
|
|
22
22
|
// Default 'email' (top-level). Empty string disables CRM ingest.
|
|
23
23
|
crm_email_path?: string;
|
|
24
|
+
// URL every inbound delivery is forwarded to (POST, async, best-effort).
|
|
25
|
+
// Empty/absent when forwarding is disabled. Backend webhook/endpoints.go.
|
|
26
|
+
forward_url?: string;
|
|
24
27
|
created_at: string;
|
|
25
28
|
}
|
|
26
29
|
|
|
@@ -29,6 +32,13 @@ export interface Delivery {
|
|
|
29
32
|
endpoint_id: string;
|
|
30
33
|
payload: unknown;
|
|
31
34
|
received_at: string;
|
|
35
|
+
// Forwarding outcome for this delivery, present only when the endpoint has
|
|
36
|
+
// a forward_url configured (backend webhook/endpoints.go). forward_status
|
|
37
|
+
// is the HTTP status of the forward POST; last_forward_error carries the
|
|
38
|
+
// failure message when the forward attempt errored.
|
|
39
|
+
forward_status?: number;
|
|
40
|
+
last_forward_at?: string;
|
|
41
|
+
last_forward_error?: string;
|
|
32
42
|
}
|
|
33
43
|
|
|
34
44
|
export interface CreateEndpointOptions {
|
package/src/workflow.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { request } from './client';
|
|
1
|
+
import { request, requestPage } from './client';
|
|
2
2
|
import { WORKFLOW_BASE as BASE_URL } from './config';
|
|
3
3
|
import type { Exposes } from './exposes';
|
|
4
4
|
|
|
@@ -62,6 +62,20 @@ export interface WorkflowRun {
|
|
|
62
62
|
started_at?: string;
|
|
63
63
|
finished_at?: string;
|
|
64
64
|
created_at: string;
|
|
65
|
+
// Per-step output records, populated by GetRun only (backend crud.go:639
|
|
66
|
+
// returns step_outputs; ListRuns omits them). Empty array when none.
|
|
67
|
+
step_outputs?: unknown[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ListRunsOptions {
|
|
71
|
+
limit?: number; // 1-500, default 100 server-side
|
|
72
|
+
cursor?: string; // last run id from the prior page (keyset)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface WorkflowRunsPage {
|
|
76
|
+
runs: WorkflowRun[];
|
|
77
|
+
next_cursor?: string;
|
|
78
|
+
has_more?: boolean;
|
|
65
79
|
}
|
|
66
80
|
|
|
67
81
|
// Local URL-shape check on http_request steps. Same shape as the SSRF
|
|
@@ -122,8 +136,25 @@ export async function deleteWorkflow(apiKey: string, orgId: string, workflowId:
|
|
|
122
136
|
return request('DELETE', `${BASE_URL}/workflow/orgs/${encodeURIComponent(orgId)}/workflows/${encodeURIComponent(workflowId)}`, apiKey);
|
|
123
137
|
}
|
|
124
138
|
|
|
125
|
-
|
|
126
|
-
|
|
139
|
+
// listWorkflowRuns returns recent runs newest-first. The backend wraps this
|
|
140
|
+
// in a keyset-pagination envelope (limit 1-500 + cursor), so we use
|
|
141
|
+
// requestPage to preserve next_cursor — a plain `request` would drop it.
|
|
142
|
+
export async function listWorkflowRuns(
|
|
143
|
+
apiKey: string,
|
|
144
|
+
orgId: string,
|
|
145
|
+
workflowId: string,
|
|
146
|
+
opts: ListRunsOptions = {},
|
|
147
|
+
): Promise<WorkflowRunsPage> {
|
|
148
|
+
const qs = new URLSearchParams();
|
|
149
|
+
if (opts.limit !== undefined) qs.set('limit', String(opts.limit));
|
|
150
|
+
if (opts.cursor) qs.set('cursor', opts.cursor);
|
|
151
|
+
const query = qs.toString() ? `?${qs.toString()}` : '';
|
|
152
|
+
const page = await requestPage<WorkflowRun>(
|
|
153
|
+
'GET',
|
|
154
|
+
`${BASE_URL}/workflow/orgs/${encodeURIComponent(orgId)}/workflows/${encodeURIComponent(workflowId)}/runs${query}`,
|
|
155
|
+
apiKey,
|
|
156
|
+
);
|
|
157
|
+
return { runs: page.data, next_cursor: page.next_cursor, has_more: page.has_more };
|
|
127
158
|
}
|
|
128
159
|
|
|
129
160
|
export async function getWorkflowRun(apiKey: string, orgId: string, runId: string): Promise<WorkflowRun> {
|