@myapihq/sdk 2.4.2 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.ts CHANGED
@@ -25,6 +25,14 @@ export interface RequestOptions {
25
25
  retryUnsafe?: boolean;
26
26
  /** Caller-supplied AbortSignal, composed with the timeout. */
27
27
  signal?: AbortSignal;
28
+ /**
29
+ * Called when the backend answered from its idempotency cache instead of
30
+ * executing — i.e. a retry collapsed into the original request rather than
31
+ * running again. "Succeeded" and "already succeeded" are different facts to
32
+ * an agent deciding whether to count a charge, so this is surfaced rather
33
+ * than swallowed.
34
+ */
35
+ onReplay?: () => void;
28
36
  }
29
37
  export declare function request<T>(method: string, url: string, apiKey?: string, body?: unknown, extraHeaders?: Record<string, string>, opts?: RequestOptions): Promise<T>;
30
38
  export interface Page<T> {
package/dist/client.js CHANGED
@@ -196,9 +196,39 @@ async function requestFull(method, url, apiKey, body, extraHeaders, opts) {
196
196
  await sleep(after ?? backoffMs(attempt));
197
197
  continue;
198
198
  }
199
+ // Idempotency-Replayed: true means the backend served its cached response
200
+ // for this key and never ran the handler. Only meaningful on the billable
201
+ // endpoints that opt into retry, but the header is cheap to check.
202
+ if (opts?.onReplay && response.headers.get('idempotency-replayed') === 'true') {
203
+ opts.onReplay();
204
+ }
199
205
  return await parseResponse(upper, url, response);
200
206
  }
201
207
  }
208
+ // A bare "invalid_json_response" reads as a platform fault. A user building on
209
+ // MyAPI hit it three times on an oversized container deploy and, in their
210
+ // words, "went looking for an outage" — the actual cause was a 198 MB upload
211
+ // context. The status code and the shape of the body usually say which side
212
+ // the problem is on, so say it rather than making the caller guess.
213
+ function diagnoseNonJson(status, raw) {
214
+ const looksHtml = /^\s*<(?:!doctype|html)/i.test(raw);
215
+ if (status === 413) {
216
+ return '→ The request body was too large. This is a client-side limit, not an outage — send less (for a deploy, exclude build artefacts and node_modules from the upload context).';
217
+ }
218
+ if (status === 0 || status === 502 || status === 503 || status === 504) {
219
+ return '→ The service did not answer. This one probably IS transient — retry shortly.';
220
+ }
221
+ if (looksHtml && status >= 500) {
222
+ return '→ An HTML error page in place of JSON means the edge replaced the origin\'s response, so the real reason is not in this body. Check the service logs for the underlying error.';
223
+ }
224
+ if (looksHtml) {
225
+ return '→ An HTML page came back instead of JSON, which usually means the request never reached the API (wrong URL, a proxy, or a captive network) rather than the API failing.';
226
+ }
227
+ if (status >= 400 && status < 500) {
228
+ return '→ A 4xx with a non-JSON body is usually the request being rejected before it reached the API — check the URL and the payload size.';
229
+ }
230
+ return '→ If the body above looks truncated or empty, the connection was probably cut mid-response.';
231
+ }
202
232
  async function parseResponse(method, url, response) {
203
233
  if (response.status === 204) {
204
234
  return { success: true, data: null, error: null, meta: {} };
@@ -213,7 +243,7 @@ async function parseResponse(method, url, response) {
213
243
  }
214
244
  catch {
215
245
  const snippet = raw.slice(0, 200).replace(/\s+/g, ' ').trim();
216
- throw new MyApiError('invalid_json_response', response.status, `${method} ${url} returned non-JSON (status ${response.status}): ${snippet}`);
246
+ throw new MyApiError('invalid_json_response', response.status, `${method} ${url} returned non-JSON (status ${response.status}): ${snippet}\n${diagnoseNonJson(response.status, raw)}`);
217
247
  }
218
248
  if (!response.ok) {
219
249
  throw toError(result?.error, response.status);
@@ -69,3 +69,6 @@ export interface DomainBinding {
69
69
  }
70
70
  export declare function bindDomain(apiKey: string, orgId: string, containerId: string, domain: string): Promise<DomainBinding>;
71
71
  export declare function unbindDomain(apiKey: string, orgId: string, containerId: string): Promise<void>;
72
+ export declare function buildLogs(apiKey: string, orgId: string, id: string, tail?: number): Promise<{
73
+ logs: string[];
74
+ }>;
package/dist/container.js CHANGED
@@ -10,6 +10,7 @@ exports.deployContainerSource = deployContainerSource;
10
10
  exports.getContainerLogs = getContainerLogs;
11
11
  exports.bindDomain = bindDomain;
12
12
  exports.unbindDomain = unbindDomain;
13
+ exports.buildLogs = buildLogs;
13
14
  const client_1 = require("./client");
14
15
  const config_1 = require("./config");
15
16
  // Backend: my-container-api per myapi-hq/internal/routes/container/. Phase 1
@@ -24,6 +25,7 @@ exports.EXPOSES = [
24
25
  'GET /container/orgs/{org_id}/containers/{id}/logs',
25
26
  'POST /container/orgs/{org_id}/containers/{id}/domain',
26
27
  'DELETE /container/orgs/{org_id}/containers/{id}/domain',
28
+ 'GET /container/orgs/{org_id}/containers/{id}/build-logs',
27
29
  ];
28
30
  async function createContainer(apiKey, orgId, payload) {
29
31
  return (0, client_1.request)('POST', `${config_1.CONTAINER_BASE}/container/orgs/${encodeURIComponent(orgId)}/containers`, apiKey, payload);
@@ -86,3 +88,13 @@ async function bindDomain(apiKey, orgId, containerId, domain) {
86
88
  async function unbindDomain(apiKey, orgId, containerId) {
87
89
  return (0, client_1.request)('DELETE', domainUrl(orgId, containerId), apiKey);
88
90
  }
91
+ // Build logs for the most recent source build, oldest first.
92
+ //
93
+ // Distinct from `logs`, which is the running container's stream and — as a
94
+ // user reported — interleaves multi-kilobyte platform audit records with your
95
+ // own output. When a `deploy --source` fails, this is the endpoint that says
96
+ // why, without the noise.
97
+ async function buildLogs(apiKey, orgId, id, tail) {
98
+ const q = tail !== undefined ? `?tail=${encodeURIComponent(String(tail))}` : '';
99
+ return (0, client_1.request)('GET', `${config_1.CONTAINER_BASE}/container/orgs/${encodeURIComponent(orgId)}/containers/${encodeURIComponent(id)}/build-logs${q}`, apiKey);
100
+ }
package/dist/email.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type RequestOptions } from './client';
1
2
  import type { Exposes } from './exposes';
2
3
  export declare const EXPOSES: Exposes;
3
4
  export interface EmailMessage {
@@ -96,7 +97,7 @@ export declare function sendEmail(apiKey: string, payload: {
96
97
  text?: string;
97
98
  template_id?: string;
98
99
  template_vars?: Record<string, string>;
99
- }): Promise<{
100
+ }, opts?: RequestOptions): Promise<{
100
101
  message_id: string;
101
102
  }>;
102
103
  export declare function getEmailStatus(apiKey: string, messageId: string): Promise<{
package/dist/email.js CHANGED
@@ -124,8 +124,10 @@ async function deleteForwarding(apiKey, address) {
124
124
  return (0, client_1.request)('DELETE', `${config_1.EMAIL_BASE}/email/mailboxes/${encodeURIComponent(address)}/forwarding`, apiKey);
125
125
  }
126
126
  // ── Sending and reading (account-scoped) ─────────────────────────────────────
127
- async function sendEmail(apiKey, payload) {
128
- return (0, client_1.request)('POST', `${config_1.EMAIL_BASE}/email/send`, apiKey, payload);
127
+ async function sendEmail(apiKey, payload, opts) {
128
+ // Retryable: deduplicated on Idempotency-Key, so a retry after a timeout
129
+ // replays rather than sending the recipient a second copy.
130
+ return (0, client_1.request)('POST', `${config_1.EMAIL_BASE}/email/send`, apiKey, payload, undefined, { retryUnsafe: true, ...opts });
129
131
  }
130
132
  async function getEmailStatus(apiKey, messageId) {
131
133
  return (0, client_1.request)('GET', `${config_1.EMAIL_BASE}/email/status/${encodeURIComponent(messageId)}`, apiKey);
package/dist/hq.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type RequestOptions } from './client';
1
2
  import type { Exposes } from './exposes';
2
3
  export declare const EXPOSES: Exposes;
3
4
  export interface Org {
@@ -58,6 +59,8 @@ export interface ApiKey {
58
59
  spend_cap_period: string;
59
60
  current_period_spend_cents?: number;
60
61
  api_key?: string;
62
+ created_at?: string;
63
+ last_used_at?: string | null;
61
64
  }
62
65
  export declare function createAnonymousAccount(): Promise<AuthResult & {
63
66
  subdomain_url: string;
@@ -135,7 +138,7 @@ export declare function getBillingUsage(apiKey: string, period?: 'month' | '30d'
135
138
  export declare function setupPayment(apiKey: string): Promise<{
136
139
  url: string;
137
140
  }>;
138
- export declare function topUp(apiKey: string, amountDollars: number): Promise<{
141
+ export declare function topUp(apiKey: string, amountDollars: number, opts?: RequestOptions): Promise<{
139
142
  new_balance_display: string;
140
143
  }>;
141
144
  export type RechargeStatus = 'succeeded' | 'failed' | 'capped' | 'no_pm' | 'pending';
@@ -192,3 +195,4 @@ export interface DoctorReport {
192
195
  };
193
196
  }
194
197
  export declare function getDoctor(apiKey: string, orgId: string): Promise<DoctorReport>;
198
+ export declare function setAccountDefaultOrg(apiKey: string, orgId: string | null): Promise<void>;
package/dist/hq.js CHANGED
@@ -31,9 +31,11 @@ exports.getAutoRecharge = getAutoRecharge;
31
31
  exports.setAutoRecharge = setAutoRecharge;
32
32
  exports.disableAutoRecharge = disableAutoRecharge;
33
33
  exports.getDoctor = getDoctor;
34
+ exports.setAccountDefaultOrg = setAccountDefaultOrg;
34
35
  const client_1 = require("./client");
35
36
  const config_1 = require("./config");
36
37
  exports.EXPOSES = [
38
+ 'PATCH /hq/account/default-org',
37
39
  'POST /hq/account/anonymous',
38
40
  'POST /hq/account/send-code',
39
41
  'POST /hq/account/verify-code',
@@ -173,8 +175,10 @@ async function getBillingUsage(apiKey, period) {
173
175
  async function setupPayment(apiKey) {
174
176
  return (0, client_1.request)('POST', `${config_1.HQ_BASE}/hq/billing/setup-payment`, apiKey);
175
177
  }
176
- async function topUp(apiKey, amountDollars) {
177
- return (0, client_1.request)('POST', `${config_1.HQ_BASE}/hq/billing/topup`, apiKey, { amount_dollars: amountDollars });
178
+ async function topUp(apiKey, amountDollars, opts) {
179
+ // Retryable: the backend deduplicates on Idempotency-Key (24h), so a retry
180
+ // after a timeout replays the original rather than topping up twice.
181
+ return (0, client_1.request)('POST', `${config_1.HQ_BASE}/hq/billing/topup`, apiKey, { amount_dollars: amountDollars }, undefined, { retryUnsafe: true, ...opts });
178
182
  }
179
183
  async function getAutoRecharge(apiKey) {
180
184
  return (0, client_1.request)('GET', `${config_1.HQ_BASE}/hq/billing/auto-recharge`, apiKey);
@@ -194,3 +198,9 @@ async function disableAutoRecharge(apiKey) {
194
198
  async function getDoctor(apiKey, orgId) {
195
199
  return (0, client_1.request)('GET', `${config_1.HQ_BASE}/hq/orgs/${encodeURIComponent(orgId)}/doctor`, apiKey);
196
200
  }
201
+ // Pin the org that `login` hands back as default_org — sticky across sessions
202
+ // AND machines, unlike the local default in ~/.myapi/config.json. Pass null to
203
+ // clear it and revert to "most recently created".
204
+ async function setAccountDefaultOrg(apiKey, orgId) {
205
+ return (0, client_1.request)('PATCH', `${config_1.HQ_BASE}/hq/account/default-org`, apiKey, { org_id: orgId });
206
+ }
@@ -1,3 +1,4 @@
1
+ import { type RequestOptions } from './client';
1
2
  import type { Exposes } from './exposes';
2
3
  export declare const EXPOSES: Exposes;
3
4
  export interface ConnectStatus {
@@ -36,7 +37,7 @@ export interface CreateChargePayload {
36
37
  }
37
38
  export declare function connect(apiKey: string, orgId: string, stripeSecretKey: string, tier?: 't0' | 't1'): Promise<ConnectStatus>;
38
39
  export declare function getConnect(apiKey: string, orgId: string): Promise<ConnectStatus>;
39
- export declare function createCharge(apiKey: string, orgId: string, payload: CreateChargePayload): Promise<CreateChargeResponse>;
40
+ export declare function createCharge(apiKey: string, orgId: string, payload: CreateChargePayload, opts?: RequestOptions): Promise<CreateChargeResponse>;
40
41
  export declare function listCharges(apiKey: string, orgId: string): Promise<Charge[]>;
41
42
  export declare function getCharge(apiKey: string, orgId: string, chargeId: string): Promise<Charge>;
42
43
  export declare function refundCharge(apiKey: string, orgId: string, chargeId: string): Promise<{
package/dist/payments.js CHANGED
@@ -39,8 +39,10 @@ async function getConnect(apiKey, orgId) {
39
39
  }
40
40
  // createCharge opens a Stripe Checkout Session on the org's connected
41
41
  // account. `every` makes it a subscription; otherwise a one-off payment.
42
- async function createCharge(apiKey, orgId, payload) {
43
- return (0, client_1.request)('POST', `${config_1.PAYMENTS_BASE}/payments/orgs/${encodeURIComponent(orgId)}/charges`, apiKey, payload);
42
+ async function createCharge(apiKey, orgId, payload, opts) {
43
+ // Retryable: deduplicated on Idempotency-Key, so a retried charge cannot
44
+ // double-charge — it replays the original response.
45
+ return (0, client_1.request)('POST', `${config_1.PAYMENTS_BASE}/payments/orgs/${encodeURIComponent(orgId)}/charges`, apiKey, payload, undefined, { retryUnsafe: true, ...opts });
44
46
  }
45
47
  async function listCharges(apiKey, orgId) {
46
48
  return (0, client_1.request)('GET', `${config_1.PAYMENTS_BASE}/payments/orgs/${encodeURIComponent(orgId)}/charges`, apiKey);
package/dist/pixel.d.ts CHANGED
@@ -66,3 +66,7 @@ export declare function getIdentity(apiKey: string, orgId: string, pixelId: stri
66
66
  latency_ms: number;
67
67
  }>;
68
68
  export declare function getGeoSample(apiKey: string, orgId: string): Promise<Record<string, unknown>>;
69
+ export declare function identify(apiKey: string, orgId: string, pixelId: string, who: {
70
+ email?: string;
71
+ externalId?: string;
72
+ }): Promise<Record<string, unknown>>;
package/dist/pixel.js CHANGED
@@ -6,9 +6,11 @@ exports.getVisits = getVisits;
6
6
  exports.getEvents = getEvents;
7
7
  exports.getIdentity = getIdentity;
8
8
  exports.getGeoSample = getGeoSample;
9
+ exports.identify = identify;
9
10
  const client_1 = require("./client");
10
11
  const config_1 = require("./config");
11
12
  exports.EXPOSES = [
13
+ 'POST /pixel/orgs/{org_id}/identify',
12
14
  'GET /pixel/orgs/{org_id}/interactions',
13
15
  'GET /pixel/orgs/{org_id}/visits',
14
16
  'GET /pixel/orgs/{org_id}/events',
@@ -59,3 +61,16 @@ async function getIdentity(apiKey, orgId, pixelId, website) {
59
61
  async function getGeoSample(apiKey, orgId) {
60
62
  return (0, client_1.request)('GET', `${config_1.PIXEL_BASE}/pixel/orgs/${encodeURIComponent(orgId)}/audience/get_geo_sample`, apiKey);
61
63
  }
64
+ // Link a known identity to an anonymous pixel visitor.
65
+ //
66
+ // The write half of identity resolution: call it on form submit or straight
67
+ // after sign-in and the visitor's whole prior anonymous graph resolves to that
68
+ // person. Without it, `pixel identity` can only ever report anonymous nodes.
69
+ async function identify(apiKey, orgId, pixelId, who) {
70
+ const body = { pixel_id: pixelId };
71
+ if (who.email)
72
+ body.email = who.email;
73
+ if (who.externalId)
74
+ body.external_id = who.externalId;
75
+ return (0, client_1.request)('POST', `${config_1.PIXEL_BASE}/pixel/orgs/${encodeURIComponent(orgId)}/identify`, apiKey, body);
76
+ }
package/dist/storage.d.ts CHANGED
@@ -8,6 +8,7 @@ export interface Asset {
8
8
  }
9
9
  export declare function ingestAsset(apiKey: string, orgId: string, url: string, name?: string): Promise<Asset>;
10
10
  export type UploadContentType = 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp' | 'image/svg+xml' | 'application/pdf' | 'video/mp4' | 'video/webm';
11
- export declare function uploadAsset(apiKey: string, orgId: string, file: Blob | Buffer, contentType: UploadContentType, name?: string): Promise<Asset>;
11
+ export type UploadContentTypeInput = UploadContentType | (string & {});
12
+ export declare function uploadAsset(apiKey: string, orgId: string, file: Blob | Buffer, contentType: UploadContentTypeInput, name?: string): Promise<Asset>;
12
13
  export declare function listAssets(apiKey: string, orgId: string): Promise<Asset[]>;
13
14
  export declare function deleteAsset(apiKey: string, orgId: string, assetId: string): Promise<void>;
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "2.4.2";
1
+ export declare const SDK_VERSION = "2.5.0";
package/dist/version.js CHANGED
@@ -8,4 +8,4 @@ exports.SDK_VERSION = void 0;
8
8
  // Why a constant and not a package.json read: the SDK runs inside edge
9
9
  // functions (Cloudflare Workers), so it must not import node:fs. A literal
10
10
  // is the only version source that works in every runtime we ship to.
11
- exports.SDK_VERSION = '2.4.2';
11
+ exports.SDK_VERSION = '2.5.0';
package/dist/webhook.d.ts CHANGED
@@ -20,6 +20,19 @@ export interface Delivery {
20
20
  last_forward_at?: string;
21
21
  last_forward_error?: string;
22
22
  }
23
+ export interface ListDeliveriesOptions {
24
+ /** Restrict to one endpoint. An id the org doesn't own is a 404, not an empty page. */
25
+ endpointId?: string;
26
+ /** 1-200, default 50 server-side. */
27
+ limit?: number;
28
+ /** `next_cursor` from the previous page. An unknown cursor is a 400, never a silent restart. */
29
+ cursor?: string;
30
+ }
31
+ export interface DeliveriesPage {
32
+ deliveries: Delivery[];
33
+ next_cursor?: string;
34
+ has_more?: boolean;
35
+ }
23
36
  export interface CreateEndpointOptions {
24
37
  description?: string;
25
38
  crm_email_path?: string;
@@ -36,3 +49,4 @@ export declare function updateEndpoint(apiKey: string, orgId: string, endpointId
36
49
  export declare function listEndpoints(apiKey: string, orgId: string): Promise<WebhookEndpoint[]>;
37
50
  export declare function deleteEndpoint(apiKey: string, orgId: string, endpointId: string): Promise<void>;
38
51
  export declare function getDelivery(apiKey: string, orgId: string, deliveryId: string): Promise<Delivery>;
52
+ export declare function listDeliveries(apiKey: string, orgId: string, opts?: ListDeliveriesOptions): Promise<DeliveriesPage>;
package/dist/webhook.js CHANGED
@@ -6,6 +6,7 @@ exports.updateEndpoint = updateEndpoint;
6
6
  exports.listEndpoints = listEndpoints;
7
7
  exports.deleteEndpoint = deleteEndpoint;
8
8
  exports.getDelivery = getDelivery;
9
+ exports.listDeliveries = listDeliveries;
9
10
  const client_1 = require("./client");
10
11
  const config_1 = require("./config");
11
12
  exports.EXPOSES = [
@@ -14,6 +15,7 @@ exports.EXPOSES = [
14
15
  'PATCH /webhook/orgs/{org_id}/endpoints/{endpoint_id}',
15
16
  'DELETE /webhook/orgs/{org_id}/endpoints/{endpoint_id}',
16
17
  'GET /webhook/orgs/{org_id}/deliveries/{delivery_id}',
18
+ 'GET /webhook/orgs/{org_id}/deliveries',
17
19
  ];
18
20
  async function createEndpoint(apiKey, orgId, name, opts = {}) {
19
21
  const body = { name };
@@ -37,3 +39,28 @@ async function deleteEndpoint(apiKey, orgId, endpointId) {
37
39
  async function getDelivery(apiKey, orgId, deliveryId) {
38
40
  return (0, client_1.request)('GET', `${config_1.WEBHOOK_BASE}/webhook/orgs/${encodeURIComponent(orgId)}/deliveries/${encodeURIComponent(deliveryId)}`, apiKey);
39
41
  }
42
+ // listDeliveries — the deliveries an endpoint has received, newest first.
43
+ //
44
+ // This closes the gap that made webhooks unreadable to the agent that built
45
+ // them: `getDelivery` needs an id, and that id is only ever returned to
46
+ // whoever POSTed — for a funnel form, the visitor's browser. Without a list,
47
+ // an agent could not see the submissions its own form collected.
48
+ //
49
+ // Keyset-paginated (backend `envelope.WrapPage`), so use requestPage to keep
50
+ // the cursor. Two backend behaviours worth knowing: an endpoint_id the org
51
+ // doesn't own is a 404 rather than an empty page (an empty 200 would confirm
52
+ // the id exists to someone guessing), and an unknown cursor is a 400 rather
53
+ // than a silent restart from the top (a polling agent handed "start over"
54
+ // would reprocess every lead it had already handled).
55
+ async function listDeliveries(apiKey, orgId, opts = {}) {
56
+ const qs = new URLSearchParams();
57
+ if (opts.endpointId)
58
+ qs.set('endpoint_id', opts.endpointId);
59
+ if (opts.limit !== undefined)
60
+ qs.set('limit', String(opts.limit));
61
+ if (opts.cursor)
62
+ qs.set('cursor', opts.cursor);
63
+ const query = qs.toString() ? `?${qs.toString()}` : '';
64
+ const page = await (0, client_1.requestPage)('GET', `${config_1.WEBHOOK_BASE}/webhook/orgs/${encodeURIComponent(orgId)}/deliveries${query}`, apiKey);
65
+ return { deliveries: page.data, next_cursor: page.next_cursor, has_more: page.has_more };
66
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@myapihq/sdk",
3
3
  "license": "Apache-2.0",
4
- "version": "2.4.2",
4
+ "version": "2.5.0",
5
5
  "description": "TypeScript SDK for the MyAPI ecosystem",
6
6
  "repository": {
7
7
  "type": "git",