@myapihq/sdk 1.3.13 → 2.0.1

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/services.js CHANGED
@@ -28,6 +28,15 @@ exports.SERVICES = [
28
28
  status: 'ga',
29
29
  keywords: k('auth', 'identity', 'billing', 'organization'),
30
30
  },
31
+ {
32
+ module: 'auth',
33
+ skill: 'my-auth-api',
34
+ domain: 'myauthapi.com',
35
+ 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.',
36
+ category: 'identity',
37
+ status: 'ga',
38
+ keywords: k('auth', 'oidc', 'oauth', 'login', 'identity', 'sso'),
39
+ },
31
40
  {
32
41
  module: 'domain',
33
42
  skill: 'my-domain-api',
@@ -46,16 +55,16 @@ exports.SERVICES = [
46
55
  module: 'email',
47
56
  skill: 'my-email-api',
48
57
  domain: 'myemailapi.com',
49
- description: 'Send transactional and bulk email from your own domain. Mailboxes, AI templates, drip campaigns, and warmup.',
58
+ description: 'Send transactional and bulk email from your own domain. Mailboxes, AI templates, and warmup.',
50
59
  category: 'send',
51
60
  status: 'ga',
52
- keywords: k('email', 'transactional', 'campaign', 'smtp'),
61
+ keywords: k('email', 'transactional', 'smtp', 'mailbox'),
53
62
  },
54
63
  {
55
64
  module: 'email',
56
65
  skill: 'my-email-verify-api',
57
66
  domain: 'myemailapi.com',
58
- description: 'Sync single-address email verification — syntax + DNS + Microsoft probe. Pre-send quality gate for campaigns.',
67
+ description: 'Sync single-address email verification — syntax + DNS + Microsoft probe. Pre-send quality gate for outbound.',
59
68
  category: 'send',
60
69
  status: 'ga',
61
70
  keywords: k('email', 'verification', 'deliverability', 'syntax', 'dns'),
@@ -175,10 +184,10 @@ exports.SERVICES = [
175
184
  module: 'llm',
176
185
  skill: 'my-llm-api',
177
186
  domain: 'myllmapi.com',
178
- 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.',
187
+ 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.',
179
188
  category: 'compute',
180
189
  status: 'ga',
181
- keywords: k('llm', 'completion', 'embedding', 'gemini'),
190
+ keywords: k('llm', 'completion', 'qwen', 'classify'),
182
191
  },
183
192
  {
184
193
  // CLI top-level command is `fn`; SDK namespace is `fn`; skill directory is
@@ -236,7 +245,7 @@ exports.SERVICES = [
236
245
  domain: 'mygitapi.com',
237
246
  description: 'Hosted git repositories over HTTP — create repos, commit files, manage branches and tags, read trees, blobs, history, and diffs. No local clone required.',
238
247
  category: 'store',
239
- status: 'preview',
248
+ status: 'ga',
240
249
  keywords: k('git', 'repository', 'version-control', 'commit', 'scm'),
241
250
  },
242
251
  // ── orchestrate ─────────────────────────────────────────────────────
package/dist/storage.d.ts CHANGED
@@ -7,7 +7,7 @@ export interface Asset {
7
7
  created_at: string;
8
8
  }
9
9
  export declare function ingestAsset(apiKey: string, orgId: string, url: string, name?: string): Promise<Asset>;
10
- export type UploadContentType = 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp' | 'video/mp4' | 'video/webm';
10
+ export type UploadContentType = 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp' | 'image/svg+xml' | 'application/pdf' | 'video/mp4' | 'video/webm';
11
11
  export declare function uploadAsset(apiKey: string, orgId: string, file: Blob | Buffer, contentType: UploadContentType, name?: string): Promise<Asset>;
12
12
  export declare function listAssets(apiKey: string, orgId: string): Promise<Asset[]>;
13
13
  export declare function deleteAsset(apiKey: string, orgId: string, assetId: string): Promise<void>;
package/dist/types.d.ts CHANGED
@@ -7,6 +7,9 @@ export interface ApiResponse<T> {
7
7
  latency_ms: number;
8
8
  service: string;
9
9
  version: string;
10
+ next_cursor?: string;
11
+ has_more?: boolean;
12
+ limit?: number;
10
13
  };
11
14
  }
12
15
  export interface PaginatedResponse<T> {
package/dist/webhook.d.ts CHANGED
@@ -8,6 +8,7 @@ export interface WebhookEndpoint {
8
8
  slug: string;
9
9
  url: string;
10
10
  crm_email_path?: string;
11
+ forward_url?: string;
11
12
  created_at: string;
12
13
  }
13
14
  export interface Delivery {
@@ -15,6 +16,9 @@ export interface Delivery {
15
16
  endpoint_id: string;
16
17
  payload: unknown;
17
18
  received_at: string;
19
+ forward_status?: number;
20
+ last_forward_at?: string;
21
+ last_forward_error?: string;
18
22
  }
19
23
  export interface CreateEndpointOptions {
20
24
  description?: string;
@@ -58,6 +58,16 @@ export interface WorkflowRun {
58
58
  started_at?: string;
59
59
  finished_at?: string;
60
60
  created_at: string;
61
+ step_outputs?: unknown[];
62
+ }
63
+ export interface ListRunsOptions {
64
+ limit?: number;
65
+ cursor?: string;
66
+ }
67
+ export interface WorkflowRunsPage {
68
+ runs: WorkflowRun[];
69
+ next_cursor?: string;
70
+ has_more?: boolean;
61
71
  }
62
72
  export declare function createWorkflow(apiKey: string, orgId: string, payload: {
63
73
  name: string;
@@ -78,5 +88,5 @@ export declare function updateWorkflow(apiKey: string, orgId: string, workflowId
78
88
  export declare function enableWorkflow(apiKey: string, orgId: string, workflowId: string): Promise<void>;
79
89
  export declare function disableWorkflow(apiKey: string, orgId: string, workflowId: string): Promise<void>;
80
90
  export declare function deleteWorkflow(apiKey: string, orgId: string, workflowId: string): Promise<void>;
81
- export declare function listWorkflowRuns(apiKey: string, orgId: string, workflowId: string): Promise<WorkflowRun[]>;
91
+ export declare function listWorkflowRuns(apiKey: string, orgId: string, workflowId: string, opts?: ListRunsOptions): Promise<WorkflowRunsPage>;
82
92
  export declare function getWorkflowRun(apiKey: string, orgId: string, runId: string): Promise<WorkflowRun>;
package/dist/workflow.js CHANGED
@@ -72,8 +72,18 @@ async function disableWorkflow(apiKey, orgId, workflowId) {
72
72
  async function deleteWorkflow(apiKey, orgId, workflowId) {
73
73
  return (0, client_1.request)('DELETE', `${config_1.WORKFLOW_BASE}/workflow/orgs/${encodeURIComponent(orgId)}/workflows/${encodeURIComponent(workflowId)}`, apiKey);
74
74
  }
75
- async function listWorkflowRuns(apiKey, orgId, workflowId) {
76
- return (0, client_1.request)('GET', `${config_1.WORKFLOW_BASE}/workflow/orgs/${encodeURIComponent(orgId)}/workflows/${encodeURIComponent(workflowId)}/runs`, apiKey);
75
+ // listWorkflowRuns returns recent runs newest-first. The backend wraps this
76
+ // in a keyset-pagination envelope (limit 1-500 + cursor), so we use
77
+ // requestPage to preserve next_cursor — a plain `request` would drop it.
78
+ async function listWorkflowRuns(apiKey, orgId, workflowId, opts = {}) {
79
+ const qs = new URLSearchParams();
80
+ if (opts.limit !== undefined)
81
+ qs.set('limit', String(opts.limit));
82
+ if (opts.cursor)
83
+ qs.set('cursor', opts.cursor);
84
+ const query = qs.toString() ? `?${qs.toString()}` : '';
85
+ const page = await (0, client_1.requestPage)('GET', `${config_1.WORKFLOW_BASE}/workflow/orgs/${encodeURIComponent(orgId)}/workflows/${encodeURIComponent(workflowId)}/runs${query}`, apiKey);
86
+ return { runs: page.data, next_cursor: page.next_cursor, has_more: page.has_more };
77
87
  }
78
88
  async function getWorkflowRun(apiKey, orgId, runId) {
79
89
  return (0, client_1.request)('GET', `${config_1.WORKFLOW_BASE}/workflow/orgs/${encodeURIComponent(orgId)}/runs/${encodeURIComponent(runId)}`, apiKey);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@myapihq/sdk",
3
3
  "license": "Apache-2.0",
4
- "version": "1.3.13",
4
+ "version": "2.0.1",
5
5
  "description": "TypeScript SDK for the MyAPI ecosystem",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
package/src/auth.ts ADDED
@@ -0,0 +1,129 @@
1
+ import { request } from './client';
2
+ import { AUTH_BASE as BASE_URL } from './config';
3
+ import type { Exposes } from './exposes';
4
+
5
+ // The end-user auth product (my-auth-api): a managed multi-tenant OIDC
6
+ // Identity Provider for the END USERS of apps built on MyAPI. Distinct from
7
+ // operator/account auth (hq/account/*). Each org gets one auth tenant; OIDC
8
+ // clients (apps) register against it. End users sign in with managed Google,
9
+ // email/password, or magic links (per the tenant's `connections`). Tokens are
10
+ // RS256, verified via the tenant's JWKS.
11
+ //
12
+ // The browser/app-facing OIDC + sign-in flows — discovery, jwks, authorize,
13
+ // token, userinfo, password/magic/google login, verify-email, password reset,
14
+ // the hosted login page — are machine/end-user surfaces consumed by apps, the
15
+ // JS SDK, and browsers, NOT management calls; they are not CLI/SDK verbs. This
16
+ // module covers only the org-scoped MANAGEMENT surface: tenant, clients, MAU
17
+ // usage, and the custom auth domain.
18
+ export const EXPOSES: Exposes = [
19
+ 'POST /auth/orgs/{org_id}/tenant',
20
+ 'GET /auth/orgs/{org_id}/tenant',
21
+ 'POST /auth/orgs/{org_id}/clients',
22
+ 'GET /auth/orgs/{org_id}/clients',
23
+ 'GET /auth/orgs/{org_id}/usage',
24
+ 'POST /auth/orgs/{org_id}/domain',
25
+ 'GET /auth/orgs/{org_id}/domain',
26
+ 'DELETE /auth/orgs/{org_id}/domain',
27
+ ];
28
+
29
+ // Sign-in methods a tenant offers on its hosted login page.
30
+ export type AuthConnection = 'google' | 'password' | 'magic';
31
+
32
+ export interface Tenant {
33
+ tenant_id: string;
34
+ issuer: string; // OIDC issuer: <base>/<tenant_id>
35
+ login_url: string; // hosted authorize endpoint
36
+ connections?: AuthConnection[];
37
+ }
38
+
39
+ export interface CreateTenantInput {
40
+ // Sign-in methods to enable. Defaults to ['google'] when omitted.
41
+ connections?: AuthConnection[];
42
+ // Opaque JSON for the hosted login page (branding).
43
+ theme?: unknown;
44
+ }
45
+
46
+ export interface AuthClient {
47
+ client_id: string;
48
+ name?: string;
49
+ type: 'spa' | 'web';
50
+ redirect_uris: string[];
51
+ issuer?: string;
52
+ // Returned exactly ONCE, on create, for confidential ('web') clients.
53
+ // SPA clients are public and have no secret.
54
+ client_secret?: string;
55
+ created_at?: string;
56
+ }
57
+
58
+ export interface ListClientsResponse {
59
+ clients: AuthClient[];
60
+ }
61
+
62
+ export interface CreateClientInput {
63
+ name: string;
64
+ type: 'spa' | 'web';
65
+ redirect_uris: string[];
66
+ }
67
+
68
+ // Monthly-active-user metering for the tenant (auth is billed per MAU).
69
+ export interface AuthUsage {
70
+ period: string; // 'YYYY-MM'
71
+ active_users: number;
72
+ price_cents_each: number;
73
+ }
74
+
75
+ // A custom auth domain (e.g. auth.acme.com) for the tenant's hosted login + issuer.
76
+ export interface AuthDomain {
77
+ domain: string;
78
+ status: 'pending' | 'active';
79
+ dns: { type: string; name: string; value: string };
80
+ next?: string;
81
+ issuer?: string; // present once active
82
+ login_url?: string; // present once active
83
+ }
84
+
85
+ // Create (or update) the org's auth tenant. Idempotent. `connections` selects
86
+ // the sign-in methods (subset of google/password/magic; defaults to google);
87
+ // `theme` is opaque JSON for the hosted login page.
88
+ export async function createTenant(apiKey: string, orgId: string, input: CreateTenantInput = {}): Promise<Tenant> {
89
+ const body: Record<string, unknown> = {};
90
+ if (input.connections !== undefined) body.connections = input.connections;
91
+ if (input.theme !== undefined) body.theme = input.theme;
92
+ return request('POST', `${BASE_URL}/auth/orgs/${encodeURIComponent(orgId)}/tenant`, apiKey, body);
93
+ }
94
+
95
+ // Fetch the org's auth tenant. Rejects with TENANT_NOT_FOUND if not created.
96
+ export async function getTenant(apiKey: string, orgId: string): Promise<Tenant> {
97
+ return request('GET', `${BASE_URL}/auth/orgs/${encodeURIComponent(orgId)}/tenant`, apiKey);
98
+ }
99
+
100
+ // Register an OIDC client (an app) under the org's tenant. Auto-provisions the
101
+ // tenant if absent. For type 'web' the response carries `client_secret` once.
102
+ export async function createClient(apiKey: string, orgId: string, input: CreateClientInput): Promise<AuthClient> {
103
+ return request('POST', `${BASE_URL}/auth/orgs/${encodeURIComponent(orgId)}/clients`, apiKey, input);
104
+ }
105
+
106
+ export async function listClients(apiKey: string, orgId: string): Promise<ListClientsResponse> {
107
+ return request('GET', `${BASE_URL}/auth/orgs/${encodeURIComponent(orgId)}/clients`, apiKey);
108
+ }
109
+
110
+ // Monthly-active-user usage for the current period (auth is billed per MAU).
111
+ export async function getUsage(apiKey: string, orgId: string): Promise<AuthUsage> {
112
+ return request('GET', `${BASE_URL}/auth/orgs/${encodeURIComponent(orgId)}/usage`, apiKey);
113
+ }
114
+
115
+ // Set a custom auth domain (e.g. auth.acme.com). Returns the DNS record to
116
+ // create; TLS provisions automatically and the domain becomes the issuer once
117
+ // active. One custom domain per org.
118
+ export async function registerDomain(apiKey: string, orgId: string, domain: string): Promise<AuthDomain> {
119
+ return request('POST', `${BASE_URL}/auth/orgs/${encodeURIComponent(orgId)}/domain`, apiKey, { domain });
120
+ }
121
+
122
+ // Fetch the org's custom auth domain. Rejects with NO_DOMAIN if none set.
123
+ export async function getDomain(apiKey: string, orgId: string): Promise<AuthDomain> {
124
+ return request('GET', `${BASE_URL}/auth/orgs/${encodeURIComponent(orgId)}/domain`, apiKey);
125
+ }
126
+
127
+ export async function deleteDomain(apiKey: string, orgId: string, domain: string): Promise<{ domain: string; status: string }> {
128
+ return request('DELETE', `${BASE_URL}/auth/orgs/${encodeURIComponent(orgId)}/domain`, apiKey, { domain });
129
+ }
package/src/client.ts CHANGED
@@ -12,13 +12,13 @@ export class MyApiError extends Error {
12
12
  }
13
13
  }
14
14
 
15
- export async function request<T>(
15
+ async function requestFull<T>(
16
16
  method: string,
17
17
  url: string,
18
18
  apiKey?: string,
19
19
  body?: unknown,
20
20
  extraHeaders?: Record<string, string>,
21
- ): Promise<T> {
21
+ ): Promise<ApiResponse<T>> {
22
22
  const headers: Record<string, string> = {};
23
23
 
24
24
  if (apiKey) {
@@ -48,7 +48,7 @@ export async function request<T>(
48
48
  const response = await fetch(url, options);
49
49
 
50
50
  if (response.status === 204) {
51
- return undefined as T;
51
+ return { success: true, data: null, error: null, meta: {} as ApiResponse<T>['meta'] };
52
52
  }
53
53
 
54
54
  // Read the body as text first so we can include a snippet in errors when
@@ -85,5 +85,37 @@ export async function request<T>(
85
85
  throw new MyApiError(code, response.status, detail, errBody);
86
86
  }
87
87
 
88
- return apiResponse.data as T;
88
+ return apiResponse;
89
+ }
90
+
91
+ // Returns the unwrapped `data` payload — the common case.
92
+ export async function request<T>(
93
+ method: string,
94
+ url: string,
95
+ apiKey?: string,
96
+ body?: unknown,
97
+ extraHeaders?: Record<string, string>,
98
+ ): Promise<T> {
99
+ return (await requestFull<T>(method, url, apiKey, body, extraHeaders)).data as T;
100
+ }
101
+
102
+ export interface Page<T> {
103
+ data: T[];
104
+ next_cursor?: string;
105
+ has_more?: boolean;
106
+ }
107
+
108
+ // For keyset-paginated endpoints (backend `envelope.WrapPage`): `data` is a
109
+ // bare array and the cursor/has_more live under `meta`. `request` would drop
110
+ // the cursor, so list endpoints with pagination must use this.
111
+ export async function requestPage<T>(
112
+ method: string,
113
+ url: string,
114
+ apiKey?: string,
115
+ body?: unknown,
116
+ extraHeaders?: Record<string, string>,
117
+ ): Promise<Page<T>> {
118
+ const r = await requestFull<T[]>(method, url, apiKey, body, extraHeaders);
119
+ const meta = (r.meta ?? {}) as { next_cursor?: string; has_more?: boolean };
120
+ return { data: (r.data ?? []) as T[], next_cursor: meta.next_cursor, has_more: meta.has_more };
89
121
  }
package/src/config.ts CHANGED
@@ -36,3 +36,4 @@ export const AUDIENCE_BASE= process.env.MYAPI_AUDIENCE_URL?? GATEWAY;
36
36
  export const LLM_BASE = process.env.MYAPI_LLM_URL ?? GATEWAY;
37
37
  export const DATABASE_BASE= process.env.MYAPI_DATABASE_URL?? GATEWAY;
38
38
  export const CRM_BASE = process.env.MYAPI_CRM_URL ?? GATEWAY;
39
+ export const AUTH_BASE = process.env.MYAPI_AUTH_URL ?? GATEWAY;
package/src/container.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { request } from './client';
1
+ import { request, MyApiError } from './client';
2
+ import { ApiResponse } from './types';
2
3
  import { CONTAINER_BASE as BASE_URL } from './config';
3
4
  import type { Exposes } from './exposes';
4
5
 
@@ -39,6 +40,9 @@ export interface Container {
39
40
  // Origin Rule that rewrites Host + SNI to the Cloud Run hostname.
40
41
  custom_domain?: string;
41
42
  status: string;
43
+ // Egress mode for the container's outbound traffic (backend returns it on
44
+ // every row, container/crud.go). Static-IP vs. default depending on config.
45
+ egress: string;
42
46
  created_at: string;
43
47
  updated_at: string;
44
48
  }
@@ -89,12 +93,58 @@ export async function deleteContainer(apiKey: string, orgId: string, containerId
89
93
  return request('DELETE', `${BASE_URL}/container/orgs/${encodeURIComponent(orgId)}/containers/${encodeURIComponent(containerId)}`, apiKey);
90
94
  }
91
95
 
92
- // deployContainer ships a revision from a pre-built image ref to Cloud Run
93
- // (source builds land in a later backend slice). Rotates the scoped key.
96
+ // deployContainer ships a revision from a pre-built image ref to Cloud Run.
97
+ // Synchronous returns status='active' with a rotated scoped key.
94
98
  export async function deployContainer(apiKey: string, orgId: string, containerId: string, image: string): Promise<DeployResponse> {
95
99
  return request('POST', `${BASE_URL}/container/orgs/${encodeURIComponent(orgId)}/containers/${encodeURIComponent(containerId)}/deploy`, apiKey, { image });
96
100
  }
97
101
 
102
+ // BuildDeployResponse — the async source-build path. The backend records a
103
+ // `building` revision, kicks off Cloud Build on a background goroutine, and
104
+ // returns immediately. The scoped key is minted later (once the image
105
+ // exists), so it is NOT in this response. Poll getContainer until
106
+ // status='active' (or 'build_error'/'deploy_error').
107
+ export interface BuildDeployResponse {
108
+ container_id: string;
109
+ revision_id: string;
110
+ status: string; // 'building'
111
+ message?: string;
112
+ }
113
+
114
+ // deployContainerSource uploads a build-context tarball via multipart `source`
115
+ // (backend deploy.go builds it via Cloud Build → Artifact Registry, then
116
+ // deploys). Asynchronous: returns status='building' to poll. The tarball is
117
+ // capped at 100MB server-side. Mirrors the multipart upload in storage.ts.
118
+ export async function deployContainerSource(
119
+ apiKey: string,
120
+ orgId: string,
121
+ containerId: string,
122
+ tarball: Blob | Buffer,
123
+ filename = 'source.tar.gz',
124
+ ): Promise<BuildDeployResponse> {
125
+ const formData = new FormData();
126
+ formData.append('source', new Blob([tarball as any], { type: 'application/gzip' }), filename);
127
+
128
+ const response = await fetch(
129
+ `${BASE_URL}/container/orgs/${encodeURIComponent(orgId)}/containers/${encodeURIComponent(containerId)}/deploy`,
130
+ { method: 'POST', headers: { Authorization: `Bearer ${apiKey}` }, body: formData as any },
131
+ );
132
+
133
+ let result: any;
134
+ try {
135
+ result = await response.json();
136
+ } catch {
137
+ throw new MyApiError('invalid_json_response', response.status);
138
+ }
139
+ if (!response.ok || !result?.success) {
140
+ const err = result?.error;
141
+ const code = typeof err === 'object' ? (err?.code || 'unknown_error') : (err || 'unknown_error');
142
+ const detail = typeof err === 'object' ? err?.message : undefined;
143
+ throw new MyApiError(code, response.status, detail, typeof err === 'object' ? err : undefined);
144
+ }
145
+ return (result as ApiResponse<BuildDeployResponse>).data as BuildDeployResponse;
146
+ }
147
+
98
148
  // One runtime log line from the container's Cloud Run resource.
99
149
  export interface LogEntry {
100
150
  timestamp: string;
package/src/crm.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { request } from './client';
1
+ import { request, requestPage } from './client';
2
2
  import { CRM_BASE as BASE_URL } from './config';
3
3
  import type { Exposes } from './exposes';
4
4
 
@@ -46,7 +46,8 @@ export type EventKind =
46
46
  | 'email_clicked'
47
47
  | 'email_replied'
48
48
  | 'pixel_visit'
49
- | 'webhook_received';
49
+ | 'webhook_received'
50
+ | 'payment';
50
51
 
51
52
  // Goldfox enrichment is a live join — the response embeds the current
52
53
  // Goldfox row when goldfox_person_id is set. v1: returns null until the
@@ -204,7 +205,10 @@ export async function getContactEvents(apiKey: string, orgId: string, id: string
204
205
  if (opts.kind != null) qs.append('kind', opts.kind);
205
206
  const q = qs.toString();
206
207
  const url = `${BASE_URL}/crm/orgs/${encodeURIComponent(orgId)}/contacts/${encodeURIComponent(id)}/events${q ? `?${q}` : ''}`;
207
- return request('GET', url, apiKey);
208
+ // Paginated (WrapPage): events are the bare `data` array with the cursor
209
+ // under `meta` — use requestPage so `next_cursor` survives.
210
+ const page = await requestPage<ContactEvent>('GET', url, apiKey);
211
+ return { events: page.data, next_cursor: page.next_cursor };
208
212
  }
209
213
 
210
214
  // ── Companies ─────────────────────────────────────────────────────────────
package/src/database.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { request } from './client';
1
+ import { request, requestPage } from './client';
2
2
  import { DATABASE_BASE as BASE_URL } from './config';
3
3
  import type { Exposes } from './exposes';
4
4
 
@@ -71,7 +71,10 @@ export async function listKeys(apiKey: string, orgId: string, ns: string, opts:
71
71
  if (opts.values) qs.append('values', 'true');
72
72
  const q = qs.toString();
73
73
  const url = `${BASE_URL}/database/orgs/${encodeURIComponent(orgId)}/namespaces/${encodeURIComponent(ns)}/keys${q ? `?${q}` : ''}`;
74
- return request('GET', url, apiKey);
74
+ // Paginated (WrapPage): keys come back as the bare `data` array with the
75
+ // cursor under `meta` — must use requestPage or the cursor is lost.
76
+ const page = await requestPage<KeyEntry>('GET', url, apiKey);
77
+ return { keys: page.data, next_cursor: page.next_cursor };
75
78
  }
76
79
 
77
80
  export async function getKey(apiKey: string, orgId: string, ns: string, key: string): Promise<KeyEntry> {
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/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/index.ts CHANGED
@@ -7,6 +7,7 @@ export * from './client';
7
7
  // CJS-compiled SDK. If you need a base URL in a downstream package, mirror
8
8
  // the constant locally — it's one line and the comment makes it obvious.
9
9
  export * as hq from './hq';
10
+ export * as auth from './auth';
10
11
  export * as domain from './domain';
11
12
  export * as email from './email';
12
13
  export * as funnel from './funnel';