@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.
Files changed (54) hide show
  1. package/dist/auth.d.ts +58 -0
  2. package/dist/auth.js +76 -0
  3. package/dist/client.d.ts +6 -0
  4. package/dist/client.js +16 -3
  5. package/dist/config.d.ts +1 -0
  6. package/dist/config.js +2 -1
  7. package/dist/container.d.ts +8 -0
  8. package/dist/container.js +26 -2
  9. package/dist/crm.d.ts +1 -1
  10. package/dist/crm.js +4 -1
  11. package/dist/database.js +4 -1
  12. package/dist/domain.d.ts +1 -1
  13. package/dist/email.d.ts +0 -74
  14. package/dist/email.js +1 -70
  15. package/dist/function.d.ts +4 -0
  16. package/dist/function.js +8 -0
  17. package/dist/funds.d.ts +11 -0
  18. package/dist/funds.js +55 -0
  19. package/dist/funnel.d.ts +1 -1
  20. package/dist/hq.d.ts +22 -0
  21. package/dist/hq.js +18 -0
  22. package/dist/index.d.ts +2 -0
  23. package/dist/index.js +3 -1
  24. package/dist/payments.d.ts +1 -0
  25. package/dist/payments.js +1 -1
  26. package/dist/queue.d.ts +1 -0
  27. package/dist/queue.js +5 -0
  28. package/dist/services.js +17 -8
  29. package/dist/storage.d.ts +1 -1
  30. package/dist/types.d.ts +3 -0
  31. package/dist/webhook.d.ts +4 -0
  32. package/dist/workflow.d.ts +11 -1
  33. package/dist/workflow.js +12 -2
  34. package/package.json +1 -1
  35. package/src/auth.ts +129 -0
  36. package/src/client.ts +36 -4
  37. package/src/config.ts +1 -0
  38. package/src/container.ts +53 -3
  39. package/src/crm.ts +7 -3
  40. package/src/database.ts +5 -2
  41. package/src/domain.ts +1 -1
  42. package/src/email.ts +1 -78
  43. package/src/function.ts +12 -0
  44. package/src/funds.ts +66 -0
  45. package/src/funnel.ts +1 -1
  46. package/src/hq.ts +59 -0
  47. package/src/index.ts +2 -0
  48. package/src/payments.ts +2 -1
  49. package/src/queue.ts +5 -0
  50. package/src/services.ts +17 -8
  51. package/src/storage.ts +2 -0
  52. package/src/types.ts +3 -1
  53. package/src/webhook.ts +10 -0
  54. 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
- email_subdomain: string;
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
- // Returns 409 MAILBOX_IN_USE when an active/paused campaign still sends from
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. Throws NOT_CONNECTED (404)
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, campaigns, templates,
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, drip campaigns, and warmup.',
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', 'campaign', 'smtp'),
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 campaigns.',
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: 'Provider-agnostic LLM completions + embeddings via MyAPI. Routes to managed models (Gemini today, more soon) at upstream cost. Use in workflow steps, not for your own reasoning.',
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', 'embedding', 'gemini'),
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: 'preview',
279
+ status: 'ga',
271
280
  keywords: k('git', 'repository', 'version-control', 'commit', 'scm'),
272
281
  },
273
282
 
package/src/storage.ts CHANGED
@@ -27,6 +27,8 @@ export type UploadContentType =
27
27
  | 'image/png'
28
28
  | 'image/gif'
29
29
  | 'image/webp'
30
+ | 'image/svg+xml'
31
+ | 'application/pdf'
30
32
  | 'video/mp4'
31
33
  | 'video/webm';
32
34
 
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
- meta: { request_id: string; latency_ms: number; service: string; version: string };
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
- export async function listWorkflowRuns(apiKey: string, orgId: string, workflowId: string): Promise<WorkflowRun[]> {
126
- return request('GET', `${BASE_URL}/workflow/orgs/${encodeURIComponent(orgId)}/workflows/${encodeURIComponent(workflowId)}/runs`, apiKey);
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> {