@myapihq/sdk 1.2.5 → 1.2.7

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@myapihq/sdk",
3
3
  "license": "Apache-2.0",
4
- "version": "1.2.5",
4
+ "version": "1.2.7",
5
5
  "description": "TypeScript SDK for the MyAPI ecosystem",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
package/src/config.ts CHANGED
@@ -15,6 +15,8 @@ export const FUNNEL_BASE = process.env.MYAPI_FUNNEL_URL ?? 'https://api.myfunn
15
15
  // New slots — routed via the gateway until brand hosts are DNS-wired
16
16
  // (mirrors the LLM/DATABASE/CRM pattern below).
17
17
  export const FUNCTION_BASE= process.env.MYAPI_FUNCTION_URL?? GATEWAY;
18
+ export const PAYMENTS_BASE= process.env.MYAPI_PAYMENTS_URL?? GATEWAY;
19
+ export const CONTAINER_BASE=process.env.MYAPI_CONTAINER_URL?? GATEWAY;
18
20
  export const IMAGE_BASE = process.env.MYAPI_IMAGE_URL ?? 'https://api.myimageapi.com';
19
21
  export const WEBHOOK_BASE = process.env.MYAPI_WEBHOOK_URL ?? 'https://api.mywebhookapi.com';
20
22
  export const WORKFLOW_BASE= process.env.MYAPI_WORKFLOW_URL?? 'https://api.myworkflowapi.com';
@@ -0,0 +1,90 @@
1
+ import { request } from './client';
2
+ import { CONTAINER_BASE as BASE_URL } from './config';
3
+ import type { Exposes } from './exposes';
4
+
5
+ // Backend: my-container-api per myapi-hq/internal/routes/container/. Phase 1
6
+ // is metadata + scoped API key issuance; the Cloud Run build/deploy pipeline
7
+ // is Phase 2 — `deployContainer` returns RUNTIME_UNAVAILABLE until it lands.
8
+ export const EXPOSES: Exposes = [
9
+ 'POST /container/orgs/{org_id}/containers',
10
+ 'GET /container/orgs/{org_id}/containers',
11
+ 'GET /container/orgs/{org_id}/containers/{id}',
12
+ 'DELETE /container/orgs/{org_id}/containers/{id}',
13
+ 'POST /container/orgs/{org_id}/containers/{id}/deploy',
14
+ ];
15
+
16
+ // service = HTTP server, worker = always-on background process, job = runs
17
+ // to completion (the only type that accepts a cron_schedule).
18
+ export type ContainerType = 'service' | 'worker' | 'job';
19
+
20
+ // Mirrors the backend's `Container` struct in crud.go. `url` is empty until
21
+ // the container is deployed.
22
+ export interface Container {
23
+ id: string;
24
+ org_id: string;
25
+ name: string;
26
+ type: ContainerType;
27
+ cron_schedule?: string;
28
+ env: Record<string, unknown>;
29
+ cpu: string;
30
+ memory: string;
31
+ min_instances: number;
32
+ max_instances: number;
33
+ port: number;
34
+ url?: string;
35
+ status: string;
36
+ created_at: string;
37
+ updated_at: string;
38
+ }
39
+
40
+ export interface CreatePayload {
41
+ name: string; // required; ^[a-z0-9][a-z0-9-]{0,49}$
42
+ type?: ContainerType; // defaults to 'service' server-side
43
+ cron_schedule?: string; // only valid when type is 'job'
44
+ env?: Record<string, unknown>;
45
+ cpu?: string; // defaults to '1'
46
+ memory?: string; // defaults to '512Mi'
47
+ min_instances?: number; // defaults to 0 ('worker' is forced to >= 1)
48
+ max_instances?: number; // defaults to 3
49
+ port?: number;
50
+ }
51
+
52
+ // POST response — the scoped API key is returned ONCE. It is delivered to
53
+ // the running container as the MYAPI_KEY environment variable.
54
+ export interface CreateResponse {
55
+ container: Container;
56
+ scoped_api_key: string;
57
+ scoped_api_key_id: string;
58
+ }
59
+
60
+ // deployContainer response. The scoped key is ROTATED on every deploy — the
61
+ // value here is fresh and the only time it is knowable.
62
+ export interface DeployResponse {
63
+ container_id: string;
64
+ revision_id: string;
65
+ url: string;
66
+ status: string;
67
+ scoped_api_key: string;
68
+ }
69
+
70
+ export async function createContainer(apiKey: string, orgId: string, payload: CreatePayload): Promise<CreateResponse> {
71
+ return request('POST', `${BASE_URL}/container/orgs/${encodeURIComponent(orgId)}/containers`, apiKey, payload);
72
+ }
73
+
74
+ export async function listContainers(apiKey: string, orgId: string): Promise<Container[]> {
75
+ return request('GET', `${BASE_URL}/container/orgs/${encodeURIComponent(orgId)}/containers`, apiKey);
76
+ }
77
+
78
+ export async function getContainer(apiKey: string, orgId: string, containerId: string): Promise<Container> {
79
+ return request('GET', `${BASE_URL}/container/orgs/${encodeURIComponent(orgId)}/containers/${encodeURIComponent(containerId)}`, apiKey);
80
+ }
81
+
82
+ export async function deleteContainer(apiKey: string, orgId: string, containerId: string): Promise<void> {
83
+ return request('DELETE', `${BASE_URL}/container/orgs/${encodeURIComponent(orgId)}/containers/${encodeURIComponent(containerId)}`, apiKey);
84
+ }
85
+
86
+ // deployContainer ships a revision from a pre-built image ref to Cloud Run
87
+ // (source builds land in a later backend slice). Rotates the scoped key.
88
+ export async function deployContainer(apiKey: string, orgId: string, containerId: string, image: string): Promise<DeployResponse> {
89
+ return request('POST', `${BASE_URL}/container/orgs/${encodeURIComponent(orgId)}/containers/${encodeURIComponent(containerId)}/deploy`, apiKey, { image });
90
+ }
package/src/function.ts CHANGED
@@ -1,24 +1,25 @@
1
- import { request } from './client';
1
+ import { request, MyApiError } from './client';
2
+ import { ApiResponse } from './types';
2
3
  import { FUNCTION_BASE as BASE_URL } from './config';
3
4
  import type { Exposes } from './exposes';
4
5
 
5
- // Backend: Story 1 (per myapi-hq/internal/routes/function/crud.go). Metadata
6
- // + scoped API key issuance only — Cloudflare Worker upload, /logs, and /env
7
- // land in Story 2-5. The SDK surface stays minimal until those endpoints
8
- // exist; do not add forward-compat fields here that the backend will silently
9
- // ignore.
6
+ // Backend: Story 1 (metadata + scoped API key) and Story 2/4/5 (deploy,
7
+ // runs, env) per myapi-hq/internal/routes/function/. The /logs endpoint is
8
+ // still pending do not add forward-compat fields the backend will ignore.
10
9
  export const EXPOSES: Exposes = [
11
10
  'POST /function/orgs/{org_id}/functions',
12
11
  'GET /function/orgs/{org_id}/functions',
13
12
  'GET /function/orgs/{org_id}/functions/{id}',
14
13
  'DELETE /function/orgs/{org_id}/functions/{id}',
14
+ 'POST /function/orgs/{org_id}/functions/{id}/bundle',
15
+ 'POST /function/orgs/{org_id}/functions/{id}/env',
16
+ 'GET /function/orgs/{org_id}/functions/{id}/runs',
15
17
  ];
16
18
 
17
19
  export type TriggerType = 'http' | 'cron';
18
20
 
19
21
  // Mirrors the backend's `Function` struct in crud.go. `invocation_url`
20
- // stays an empty string in Story 1; populated by Story 2 once the CF Worker
21
- // upload pipeline lands.
22
+ // stays an empty string until the function is deployed (uploadBundle).
22
23
  export interface Fn {
23
24
  id: string;
24
25
  org_id: string;
@@ -38,14 +39,33 @@ export interface CreatePayload {
38
39
 
39
40
  // POST response envelope includes the scoped API key — returned ONCE. The
40
41
  // caller must persist it if they want to use it directly; the SDK does not
41
- // store it. Story 2+ will use this key internally inside the function's
42
- // runtime shim (`globalThis.MYAPI`).
42
+ // store it. The function's runtime shim (`globalThis.MYAPI`) uses this key
43
+ // internally.
43
44
  export interface CreateResponse {
44
45
  function: Fn;
45
46
  scoped_api_key: string;
46
47
  scoped_api_key_id: string;
47
48
  }
48
49
 
50
+ // uploadBundle response. The scoped API key is ROTATED on every deploy —
51
+ // the value here is fresh and the only time it is knowable. The previous
52
+ // scoped key is revoked server-side.
53
+ export interface DeployResponse {
54
+ function: Fn;
55
+ invocation_url: string;
56
+ scoped_api_key: string;
57
+ }
58
+
59
+ // One recorded invocation — mirrors the backend's `FunctionRun` struct.
60
+ export interface FunctionRun {
61
+ id: string;
62
+ function_id: string;
63
+ invoked_at: string;
64
+ duration_ms?: number;
65
+ status?: string;
66
+ error_message?: string;
67
+ }
68
+
49
69
  export async function createFunction(apiKey: string, orgId: string, payload: CreatePayload): Promise<CreateResponse> {
50
70
  return request('POST', `${BASE_URL}/function/orgs/${encodeURIComponent(orgId)}/functions`, apiKey, payload);
51
71
  }
@@ -61,3 +81,49 @@ export async function getFunction(apiKey: string, orgId: string, fnId: string):
61
81
  export async function deleteFunction(apiKey: string, orgId: string, fnId: string): Promise<void> {
62
82
  return request('DELETE', `${BASE_URL}/function/orgs/${encodeURIComponent(orgId)}/functions/${encodeURIComponent(fnId)}`, apiKey);
63
83
  }
84
+
85
+ // uploadBundle deploys a single-file JS bundle (Story 2). The backend
86
+ // wraps it with the MYAPI shim and uploads to Cloudflare Workers. The
87
+ // multipart field MUST be named `bundle`. Raw upload cap is 4MB.
88
+ export async function uploadBundle(
89
+ apiKey: string,
90
+ orgId: string,
91
+ fnId: string,
92
+ bundle: Blob | Buffer | string,
93
+ filename = 'bundle.js',
94
+ ): Promise<DeployResponse> {
95
+ const formData = new FormData();
96
+ formData.append('bundle', new Blob([bundle as any], { type: 'application/javascript' }), filename);
97
+
98
+ const response = await fetch(
99
+ `${BASE_URL}/function/orgs/${encodeURIComponent(orgId)}/functions/${encodeURIComponent(fnId)}/bundle`,
100
+ { method: 'POST', headers: { Authorization: `Bearer ${apiKey}` }, body: formData as any },
101
+ );
102
+
103
+ let result: any;
104
+ try {
105
+ result = await response.json();
106
+ } catch {
107
+ throw new MyApiError('invalid_json_response', response.status);
108
+ }
109
+ if (!response.ok || !result?.success) {
110
+ const err = result?.error;
111
+ const code = typeof err === 'object' ? (err?.code || 'unknown_error') : (err || 'unknown_error');
112
+ const detail = typeof err === 'object' ? err?.message : undefined;
113
+ throw new MyApiError(code, response.status, detail, typeof err === 'object' ? err : undefined);
114
+ }
115
+ return (result as ApiResponse<DeployResponse>).data as DeployResponse;
116
+ }
117
+
118
+ // setFunctionEnv writes a secret (Stripe key, etc.) as a Cloudflare Worker
119
+ // Secret on the deployed script (Story 5). The function must already be
120
+ // deployed. The value is never stored in MyAPI Postgres or returned.
121
+ export async function setFunctionEnv(apiKey: string, orgId: string, fnId: string, name: string, value: string): Promise<void> {
122
+ return request('POST', `${BASE_URL}/function/orgs/${encodeURIComponent(orgId)}/functions/${encodeURIComponent(fnId)}/env`, apiKey, { name, value });
123
+ }
124
+
125
+ // listFunctionRuns returns recent invocation records, most recent first
126
+ // (Story 4). Capped at 100 server-side.
127
+ export async function listFunctionRuns(apiKey: string, orgId: string, fnId: string): Promise<FunctionRun[]> {
128
+ return request('GET', `${BASE_URL}/function/orgs/${encodeURIComponent(orgId)}/functions/${encodeURIComponent(fnId)}/runs`, apiKey);
129
+ }
package/src/funnel.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 { FUNNEL_BASE as BASE_URL } from './config';
3
4
  import type { Exposes } from './exposes';
4
5
 
@@ -10,6 +11,7 @@ export const EXPOSES: Exposes = [
10
11
  'POST /funnel/orgs/{org_id}/funnels/{funnel_id}/push-page',
11
12
  'POST /funnel/orgs/{org_id}/funnels/{funnel_id}/verify',
12
13
  'GET /funnel/orgs/{org_id}/funnels/{funnel_id}/pages',
14
+ 'POST /funnel/orgs/{org_id}/funnels/{funnel_id}/files',
13
15
  ];
14
16
 
15
17
  export interface Funnel {
@@ -73,3 +75,66 @@ export async function listFunnelPages(apiKey: string, orgId: string, funnelId: s
73
75
  export async function verifyFunnel(apiKey: string, orgId: string, funnelId: string, opts?: { html?: string; slug?: string }): Promise<VerifyResult> {
74
76
  return request('POST', `${BASE_URL}/funnel/orgs/${encodeURIComponent(orgId)}/funnels/${encodeURIComponent(funnelId)}/verify`, apiKey, opts || {});
75
77
  }
78
+
79
+ // One file in a directory publish: `path` is the path within the site
80
+ // (e.g. "index.html", "assets/app.js"); the backend roots it ("/index.html").
81
+ export interface PublishFile {
82
+ path: string;
83
+ content: Blob | Buffer | string;
84
+ }
85
+
86
+ export interface PublishOptions {
87
+ // 'dev' or 'prod' channel — defaults to 'prod' server-side.
88
+ env?: 'dev' | 'prod';
89
+ // Force SPA fallback. Auto-true server-side when an index.html is present.
90
+ spaMode?: boolean;
91
+ // Bind /api/* on the funnel to a deployed function.
92
+ apiFunctionId?: string;
93
+ }
94
+
95
+ export interface PublishResult {
96
+ manifest_id: string;
97
+ channel: 'dev' | 'prod';
98
+ file_count: number;
99
+ size_bytes: number;
100
+ spa_mode: boolean;
101
+ published_url: string;
102
+ }
103
+
104
+ // publishFiles is the my-funnel-api v2 directory publish. Every file is a
105
+ // multipart part named `files`; the part's filename carries the site path.
106
+ // 25MB total cap server-side.
107
+ export async function publishFiles(
108
+ apiKey: string,
109
+ orgId: string,
110
+ funnelId: string,
111
+ files: PublishFile[],
112
+ opts: PublishOptions = {},
113
+ ): Promise<PublishResult> {
114
+ const formData = new FormData();
115
+ for (const f of files) {
116
+ formData.append('files', new Blob([f.content as any]), f.path);
117
+ }
118
+ if (opts.env) formData.append('env', opts.env);
119
+ if (opts.spaMode !== undefined) formData.append('spa_mode', String(opts.spaMode));
120
+ if (opts.apiFunctionId) formData.append('api_function_id', opts.apiFunctionId);
121
+
122
+ const response = await fetch(
123
+ `${BASE_URL}/funnel/orgs/${encodeURIComponent(orgId)}/funnels/${encodeURIComponent(funnelId)}/files`,
124
+ { method: 'POST', headers: { Authorization: `Bearer ${apiKey}` }, body: formData as any },
125
+ );
126
+
127
+ let result: any;
128
+ try {
129
+ result = await response.json();
130
+ } catch {
131
+ throw new MyApiError('invalid_json_response', response.status);
132
+ }
133
+ if (!response.ok || !result?.success) {
134
+ const err = result?.error;
135
+ const code = typeof err === 'object' ? (err?.code || 'unknown_error') : (err || 'unknown_error');
136
+ const detail = typeof err === 'object' ? err?.message : undefined;
137
+ throw new MyApiError(code, response.status, detail, typeof err === 'object' ? err : undefined);
138
+ }
139
+ return (result as ApiResponse<PublishResult>).data as PublishResult;
140
+ }
package/src/hq.ts CHANGED
@@ -11,7 +11,9 @@ export const EXPOSES: Exposes = [
11
11
  'GET /hq/account/free-tier',
12
12
  'POST /hq/account/create/key',
13
13
  'GET /hq/account/keys',
14
+ 'POST /hq/account/keys/revoke-all',
14
15
  'DELETE /hq/account/delete/key/{key_id}',
16
+ 'PATCH /hq/account/spend-cap',
15
17
  'POST /hq/orgs',
16
18
  'GET /hq/orgs',
17
19
  'GET /hq/orgs/{org_id}',
@@ -22,6 +24,7 @@ export const EXPOSES: Exposes = [
22
24
  'POST /hq/org-imports/{import_id}/confirm',
23
25
  'GET /hq/billing/balance',
24
26
  'GET /hq/billing/history',
27
+ 'GET /hq/billing/usage',
25
28
  'POST /hq/billing/setup-payment',
26
29
  'POST /hq/billing/topup',
27
30
  ];
@@ -68,6 +71,44 @@ export interface AccountInfo {
68
71
  account_id: string;
69
72
  email?: string;
70
73
  is_anonymous?: boolean;
74
+ // Account-level spend cap (IAM "Layer 2"). Present only when a cap is set.
75
+ spend_cap_cents?: number;
76
+ current_period_spend_cents?: number;
77
+ }
78
+
79
+ // ── Capability IAM (design-iam-capability-keys-2026-05-15) ───────────────────
80
+
81
+ export type GrantAccess = 'read' | 'write';
82
+
83
+ // A key's slot grants: slot name (or "*" wildcard) → access level.
84
+ // `write` implies `read`. A slot absent from the map = no access.
85
+ export type Grants = Record<string, GrantAccess>;
86
+
87
+ // The closed grantable-slot vocabulary. Mirrors `iam.GrantableSlots` in the
88
+ // backend — keep in sync. Used to validate `--grant` client-side before the
89
+ // network call. "*" is also valid in a Grants map but is not a slot name.
90
+ export const GRANTABLE_SLOTS = [
91
+ 'domain', 'email', 'funnel', 'storage', 'image', 'webhook', 'workflow',
92
+ 'function', 'url', 'people', 'company', 'audience', 'llm', 'database', 'crm',
93
+ ] as const;
94
+
95
+ export type KeyKind = 'account' | 'function' | 'manual';
96
+ export type SpendCapPeriod = 'month' | 'day' | 'none';
97
+
98
+ // Mirrors the backend `keyView`. `api_key` is present ONLY in the create
99
+ // response (returned once). `current_period_spend_cents` is present on list
100
+ // (metered), absent on create (a new key has no spend yet).
101
+ export interface ApiKey {
102
+ id: string;
103
+ name: string;
104
+ prefix: string;
105
+ kind: KeyKind;
106
+ org_id: string | null;
107
+ grants: Grants;
108
+ spend_cap_cents: number | null;
109
+ spend_cap_period: string;
110
+ current_period_spend_cents?: number;
111
+ api_key?: string;
71
112
  }
72
113
 
73
114
  export async function createAnonymousAccount(): Promise<AuthResult & { subdomain_url: string }> {
@@ -106,11 +147,27 @@ export async function getAccount(apiKey: string): Promise<AccountInfo> {
106
147
  return request('GET', `${BASE_URL}/hq/account/me`, apiKey);
107
148
  }
108
149
 
109
- export async function createApiKey(apiKey: string, name: string): Promise<{ api_key: string; id: string; prefix: string }> {
110
- return request('POST', `${BASE_URL}/hq/account/create/key`, apiKey, { name });
150
+ export interface CreateApiKeyOptions {
151
+ // Slot grants. Omitted backend defaults to unrestricted ({"*":"write"}).
152
+ grants?: Grants;
153
+ // Lock the key to a single org. Omitted → account-wide.
154
+ orgId?: string;
155
+ // Per-key spend ceiling, in cents. Omitted → no per-key cap.
156
+ spendCapCents?: number;
157
+ }
158
+
159
+ // Mints a manual API key. The requested (grants, org, spend cap) must be a
160
+ // subset of the calling key's authority — the backend rejects escalation
161
+ // with 403 SCOPE_FORBIDDEN. The full `api_key` is in the response ONCE.
162
+ export async function createApiKey(apiKey: string, name: string, opts: CreateApiKeyOptions = {}): Promise<ApiKey> {
163
+ const body: Record<string, unknown> = { name };
164
+ if (opts.grants) body.grants = opts.grants;
165
+ if (opts.orgId) body.org_id = opts.orgId;
166
+ if (opts.spendCapCents != null) body.spend_cap_cents = opts.spendCapCents;
167
+ return request('POST', `${BASE_URL}/hq/account/create/key`, apiKey, body);
111
168
  }
112
169
 
113
- export async function listApiKeys(apiKey: string): Promise<{ id: string; name: string; prefix: string; created_at: string; last_used_at?: string }[]> {
170
+ export async function listApiKeys(apiKey: string): Promise<ApiKey[]> {
114
171
  return request('GET', `${BASE_URL}/hq/account/keys`, apiKey);
115
172
  }
116
173
 
@@ -118,6 +175,25 @@ export async function revokeApiKey(apiKey: string, keyId: string): Promise<void>
118
175
  return request('DELETE', `${BASE_URL}/hq/account/delete/key/${encodeURIComponent(keyId)}`, apiKey);
119
176
  }
120
177
 
178
+ // The kill switch. With no `kind`, revokes every active key in the account —
179
+ // including the caller's own; recovery is re-auth via login. A `kind` narrows
180
+ // it to one provenance class.
181
+ export async function revokeAllKeys(apiKey: string, kind?: KeyKind): Promise<{ revoked: number }> {
182
+ return request('POST', `${BASE_URL}/hq/account/keys/revoke-all`, apiKey, kind ? { kind } : {});
183
+ }
184
+
185
+ // Sets or clears the account-level spend ceiling (IAM "Layer 2"). Pass
186
+ // `null` to clear. `period` defaults to 'month' server-side.
187
+ export async function setAccountSpendCap(
188
+ apiKey: string,
189
+ spendCapCents: number | null,
190
+ period?: SpendCapPeriod,
191
+ ): Promise<{ spend_cap_cents: number | null; spend_cap_period: string }> {
192
+ const body: Record<string, unknown> = { spend_cap_cents: spendCapCents };
193
+ if (period) body.period = period;
194
+ return request('PATCH', `${BASE_URL}/hq/account/spend-cap`, apiKey, body);
195
+ }
196
+
121
197
  export async function createOrg(apiKey: string, payload: OrgPayload): Promise<Org> {
122
198
  return request('POST', `${BASE_URL}/hq/orgs`, apiKey, payload);
123
199
  }
@@ -158,6 +234,29 @@ export async function getBillingHistory(apiKey: string): Promise<{ type: string;
158
234
  return request('GET', `${BASE_URL}/hq/billing/history`, apiKey);
159
235
  }
160
236
 
237
+ // One service's rolled-up spend over the usage window.
238
+ export interface ServiceUsage {
239
+ service: string;
240
+ requests: number;
241
+ cost_display: string;
242
+ }
243
+
244
+ // Spend rolled up by service — the accurate "where is my money going" view.
245
+ // Aggregates every billing event, unlike the flat/capped history log.
246
+ export interface BillingUsage {
247
+ period: 'month' | '30d';
248
+ since: string;
249
+ services: ServiceUsage[];
250
+ total_display: string;
251
+ }
252
+
253
+ // getBillingUsage rolls up spend by service for a window: the current
254
+ // calendar month (default) or the trailing 30 days ('30d').
255
+ export async function getBillingUsage(apiKey: string, period?: 'month' | '30d'): Promise<BillingUsage> {
256
+ const query = period ? `?period=${encodeURIComponent(period)}` : '';
257
+ return request('GET', `${BASE_URL}/hq/billing/usage${query}`, apiKey);
258
+ }
259
+
161
260
  export async function setupPayment(apiKey: string): Promise<{ url: string }> {
162
261
  return request('POST', `${BASE_URL}/hq/billing/setup-payment`, apiKey);
163
262
  }
package/src/index.ts CHANGED
@@ -23,3 +23,5 @@ export * as llm from './llm';
23
23
  export * as database from './database';
24
24
  export * as crm from './crm';
25
25
  export * as fn from './function';
26
+ export * as payments from './payments';
27
+ export * as container from './container';
@@ -0,0 +1,97 @@
1
+ import { request } from './client';
2
+ import { PAYMENTS_BASE as BASE_URL } from './config';
3
+ import type { Exposes } from './exposes';
4
+
5
+ // Backend: my-payments-api T0 (BYO Stripe) per
6
+ // myapi-hq/internal/routes/payments/. T1 (Connect Express) is deferred —
7
+ // connect with tier 't1' returns 501. The webhook receiver is public
8
+ // (Stripe signs it); it is listed here for parity but has no SDK function.
9
+ export const EXPOSES: Exposes = [
10
+ 'POST /payments/orgs/{org_id}/connect',
11
+ 'GET /payments/orgs/{org_id}/connect',
12
+ 'POST /payments/orgs/{org_id}/charges',
13
+ 'GET /payments/orgs/{org_id}/charges',
14
+ 'GET /payments/orgs/{org_id}/charges/{id}',
15
+ 'POST /payments/orgs/{org_id}/charges/{id}/refund',
16
+ 'POST /payments/webhook/{org_id}',
17
+ ];
18
+
19
+ // Connection status — the org's Stripe link. T0 takes no platform fee, so
20
+ // `application_fee_bps` is 0.
21
+ export interface ConnectStatus {
22
+ tier: string;
23
+ stripe_account_id: string;
24
+ onboarding_status: string;
25
+ application_fee_bps?: number;
26
+ }
27
+
28
+ // Charge billing interval. Absent = a one-off payment.
29
+ export type ChargeInterval = 'month' | 'year';
30
+
31
+ // A charge — mirrors the backend's `chargeView`.
32
+ export interface Charge {
33
+ id: string;
34
+ amount_cents: number;
35
+ currency: string;
36
+ every?: ChargeInterval;
37
+ description?: string;
38
+ customer_email?: string;
39
+ status: string;
40
+ created_at: string;
41
+ succeeded_at?: string;
42
+ refunded_at?: string;
43
+ }
44
+
45
+ // CreateCharge returns a hosted Stripe Checkout URL the agent pastes into
46
+ // its frontend — not a completed payment.
47
+ export interface CreateChargeResponse {
48
+ payment_id: string;
49
+ checkout_url: string;
50
+ status: string;
51
+ }
52
+
53
+ export interface CreateChargePayload {
54
+ amount_cents: number; // required, > 0
55
+ currency?: string; // defaults to 'usd' server-side
56
+ email?: string; // pre-fills the Checkout customer
57
+ description?: string; // shows on the Checkout line item
58
+ every?: ChargeInterval; // present = a subscription
59
+ success_url?: string;
60
+ cancel_url?: string;
61
+ }
62
+
63
+ // connect links the org's own Stripe account (T0 — BYO secret key). The
64
+ // key is validated live against Stripe and stored encrypted; it never
65
+ // touches MyAPI Postgres or a log line. Pass a 't1' tier to see the
66
+ // 501 T1_DEFERRED path.
67
+ export async function connect(apiKey: string, orgId: string, stripeSecretKey: string, tier: 't0' | 't1' = 't0'): Promise<ConnectStatus> {
68
+ return request('POST', `${BASE_URL}/payments/orgs/${encodeURIComponent(orgId)}/connect`, apiKey, {
69
+ tier,
70
+ stripe_secret_key: stripeSecretKey,
71
+ });
72
+ }
73
+
74
+ // getConnect reads the org's connection status. Throws NOT_CONNECTED (404)
75
+ // when the org has not connected Stripe.
76
+ export async function getConnect(apiKey: string, orgId: string): Promise<ConnectStatus> {
77
+ return request('GET', `${BASE_URL}/payments/orgs/${encodeURIComponent(orgId)}/connect`, apiKey);
78
+ }
79
+
80
+ // createCharge opens a Stripe Checkout Session on the org's connected
81
+ // account. `every` makes it a subscription; otherwise a one-off payment.
82
+ export async function createCharge(apiKey: string, orgId: string, payload: CreateChargePayload): Promise<CreateChargeResponse> {
83
+ return request('POST', `${BASE_URL}/payments/orgs/${encodeURIComponent(orgId)}/charges`, apiKey, payload);
84
+ }
85
+
86
+ export async function listCharges(apiKey: string, orgId: string): Promise<Charge[]> {
87
+ return request('GET', `${BASE_URL}/payments/orgs/${encodeURIComponent(orgId)}/charges`, apiKey);
88
+ }
89
+
90
+ export async function getCharge(apiKey: string, orgId: string, chargeId: string): Promise<Charge> {
91
+ return request('GET', `${BASE_URL}/payments/orgs/${encodeURIComponent(orgId)}/charges/${encodeURIComponent(chargeId)}`, apiKey);
92
+ }
93
+
94
+ // refundCharge issues a full refund (v1 — partial refunds out of scope).
95
+ export async function refundCharge(apiKey: string, orgId: string, chargeId: string): Promise<{ id: string; status: string }> {
96
+ return request('POST', `${BASE_URL}/payments/orgs/${encodeURIComponent(orgId)}/charges/${encodeURIComponent(chargeId)}/refund`, apiKey);
97
+ }
package/src/services.ts CHANGED
@@ -171,7 +171,9 @@ export const SERVICES: readonly ServiceMeta[] = [
171
171
  {
172
172
  module: 'crm',
173
173
  skill: 'my-crm-api',
174
- domain: 'mycrmapi.com',
174
+ // Brand domain mycrmapi.com was unavailable; mypipelineapi.com chosen
175
+ // and acquired. (Skill stays my-crm-api — domain/skill mismatch accepted.)
176
+ domain: 'mypipelineapi.com',
175
177
  description: 'The canonical store of engaged people + companies. Auto-ingest from inbound webhooks (configurable dot-path). Fixed lifecycle_stage enum, soft delete, event timeline.',
176
178
  category: 'data',
177
179
  status: 'ga',
@@ -221,17 +223,47 @@ export const SERVICES: readonly ServiceMeta[] = [
221
223
  // CLI top-level command is `fn`; SDK namespace is `fn`; skill directory is
222
224
  // `my-function-api` (the agent-discoverable brand name).
223
225
  //
224
- // Backend status: Story 1 shipped (metadata + scoped API key issuance).
225
- // Story 2 (CF Workers upload, invocation URL), Story 4 (/logs), Story 5
226
- // (/env) pending. 'planned' reflects "partial backend, not stable GA."
226
+ // Backend status: deploy/env/runs shipped (CF Workers bundle upload,
227
+ // invocation URL, Worker Secrets, run history). /logs still pending —
228
+ // 'preview' reflects "usable, not yet stable GA."
227
229
  module: 'fn',
228
230
  skill: 'my-function-api',
229
231
  domain: 'myfunctionapi.com',
230
232
  description: 'Run JavaScript functions on the edge. HTTP or cron triggers. Pre-injected MYAPI SDK scoped to the org — no auth tokens in user code.',
231
233
  category: 'compute',
232
- status: 'planned',
234
+ status: 'preview',
233
235
  keywords: k('function', 'serverless', 'edge', 'cloudflare-workers', 'http-handler', 'cron'),
234
236
  },
235
- // Note: my-payments-api removed from registry — backend has no /payments/*
236
- // endpoints yet. Re-add when backend ships per tech-spec-my-payments-api-2026-05-14.md.
237
+ {
238
+ // CLI top-level command + SDK namespace are both `payments`; skill
239
+ // directory is `my-payments-api`.
240
+ //
241
+ // Backend status: T0 (BYO Stripe) shipped — connect, charges (Checkout
242
+ // Sessions), refunds, per-org webhook. T1 (Connect Express) is deferred
243
+ // (connect returns 501 T1_DEFERRED). 'preview' reflects "T0 usable, T1
244
+ // pending."
245
+ module: 'payments',
246
+ skill: 'my-payments-api',
247
+ domain: 'mypaymentsapi.com',
248
+ description: 'Take payments with Stripe Checkout. Connect your Stripe account, create one-off or recurring charges, and refund — hosted checkout, no card handling.',
249
+ category: 'compute',
250
+ status: 'preview',
251
+ keywords: k('payments', 'stripe', 'checkout', 'billing', 'subscription'),
252
+ },
253
+ {
254
+ // CLI top-level command + SDK namespace are both `container`; skill
255
+ // directory is `my-container-api`.
256
+ //
257
+ // Backend status: Phase 1 shipped (metadata + scoped API key). The
258
+ // Cloud Run build/deploy pipeline is Phase 2 — deploy returns
259
+ // RUNTIME_UNAVAILABLE until it lands. 'preview' reflects "usable
260
+ // surface, deploy not yet GA."
261
+ module: 'container',
262
+ skill: 'my-container-api',
263
+ domain: 'mycontainerapi.com',
264
+ description: 'Run containers on demand — long-running services, background workers, and scheduled jobs. The heavier-duty sibling of edge functions, for native deps and long execution.',
265
+ category: 'compute',
266
+ status: 'preview',
267
+ keywords: k('container', 'cloud-run', 'service', 'worker', 'job'),
268
+ },
237
269
  ] as const;