@primitivedotdev/sdk 0.16.0 → 0.18.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/api/generated/sdk.gen.js +48 -10
- package/dist/api/index.d.ts +1 -1
- package/dist/{api-CTf0cUsi.js → api-DrAZhxS-.js} +48 -10
- package/dist/{index-SK_HbwVN.d.ts → index-CHWqMBs6.d.ts} +48 -10
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/oclif/api-command.js +134 -90
- package/dist/oclif/commands/emails-latest.js +39 -34
- package/dist/oclif/commands/send.js +36 -31
- package/dist/oclif/commands/whoami.js +36 -31
- package/dist/openapi/openapi.generated.js +4 -4
- package/dist/openapi/operations.generated.js +3 -3
- package/oclif.manifest.json +220 -4
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command, Flags } from "@oclif/core";
|
|
2
2
|
import { listEmails } from "../../api/generated/sdk.gen.js";
|
|
3
3
|
import { PrimitiveApiClient } from "../../api/index.js";
|
|
4
|
-
import { extractErrorPayload, writeErrorWithHints } from "../api-command.js";
|
|
4
|
+
import { extractErrorPayload, runWithTiming, TIME_FLAG_DESCRIPTION, writeErrorWithHints, } from "../api-command.js";
|
|
5
5
|
// `primitive emails:latest` is the agent-grade shortcut for "show me
|
|
6
6
|
// the most recent inbound emails as something I can read at a glance."
|
|
7
7
|
// `emails:list-emails` returns the full JSON envelope which is great
|
|
@@ -113,43 +113,48 @@ class EmailsLatestCommand extends Command {
|
|
|
113
113
|
json: Flags.boolean({
|
|
114
114
|
description: "Print the raw response envelope (with full UUIDs and meta) as JSON on STDOUT instead of the text table. Useful for piping into `jq`, capturing ids for follow-up commands, or scripting.",
|
|
115
115
|
}),
|
|
116
|
+
time: Flags.boolean({
|
|
117
|
+
description: TIME_FLAG_DESCRIPTION,
|
|
118
|
+
}),
|
|
116
119
|
};
|
|
117
120
|
async run() {
|
|
118
121
|
const { flags } = await this.parse(EmailsLatestCommand);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
122
|
+
await runWithTiming(flags.time, async () => {
|
|
123
|
+
const apiClient = new PrimitiveApiClient({
|
|
124
|
+
apiKey: flags["api-key"],
|
|
125
|
+
baseUrl: flags["base-url"],
|
|
126
|
+
});
|
|
127
|
+
const result = await listEmails({
|
|
128
|
+
client: apiClient.client,
|
|
129
|
+
query: { limit: flags.limit },
|
|
130
|
+
responseStyle: "fields",
|
|
131
|
+
});
|
|
132
|
+
if (result.error) {
|
|
133
|
+
writeErrorWithHints(extractErrorPayload(result.error));
|
|
134
|
+
process.exitCode = 1;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const envelope = result.data;
|
|
138
|
+
if (flags.json) {
|
|
139
|
+
// Raw envelope on stdout. Mirrors the shape `emails:list-emails`
|
|
140
|
+
// emits so callers can swap one for the other when they want
|
|
141
|
+
// table vs json without remembering different command names.
|
|
142
|
+
this.log(JSON.stringify(envelope ?? null, null, 2));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const rows = envelope?.data ?? [];
|
|
146
|
+
if (rows.length === 0) {
|
|
147
|
+
process.stderr.write("No inbound emails yet. Send an email to one of your verified domains to populate this list.\n");
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const idWidth = pickIdWidth(Boolean(process.stdout.isTTY));
|
|
151
|
+
// Header on stderr so the table itself stays grep-friendly.
|
|
152
|
+
const header = `${"ID".padEnd(idWidth)} ${"RECEIVED (UTC)".padEnd(RECEIVED_DISPLAY_WIDTH)} ${"FROM".padEnd(ADDRESS_DISPLAY_WIDTH)} ${"TO".padEnd(ADDRESS_DISPLAY_WIDTH)} SUBJECT`;
|
|
153
|
+
process.stderr.write(`${header}\n`);
|
|
154
|
+
for (const row of rows) {
|
|
155
|
+
this.log(formatRow(row, idWidth));
|
|
156
|
+
}
|
|
127
157
|
});
|
|
128
|
-
if (result.error) {
|
|
129
|
-
writeErrorWithHints(extractErrorPayload(result.error));
|
|
130
|
-
process.exitCode = 1;
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
const envelope = result.data;
|
|
134
|
-
if (flags.json) {
|
|
135
|
-
// Raw envelope on stdout. Mirrors the shape `emails:list-emails`
|
|
136
|
-
// emits so callers can swap one for the other when they want
|
|
137
|
-
// table vs json without remembering different command names.
|
|
138
|
-
this.log(JSON.stringify(envelope ?? null, null, 2));
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
const rows = envelope?.data ?? [];
|
|
142
|
-
if (rows.length === 0) {
|
|
143
|
-
process.stderr.write("No inbound emails yet. Send an email to one of your verified domains to populate this list.\n");
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
const idWidth = pickIdWidth(Boolean(process.stdout.isTTY));
|
|
147
|
-
// Header on stderr so the table itself stays grep-friendly.
|
|
148
|
-
const header = `${"ID".padEnd(idWidth)} ${"RECEIVED (UTC)".padEnd(RECEIVED_DISPLAY_WIDTH)} ${"FROM".padEnd(ADDRESS_DISPLAY_WIDTH)} ${"TO".padEnd(ADDRESS_DISPLAY_WIDTH)} SUBJECT`;
|
|
149
|
-
process.stderr.write(`${header}\n`);
|
|
150
|
-
for (const row of rows) {
|
|
151
|
-
this.log(formatRow(row, idWidth));
|
|
152
|
-
}
|
|
153
158
|
}
|
|
154
159
|
}
|
|
155
160
|
export default EmailsLatestCommand;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command, Errors, Flags } from "@oclif/core";
|
|
2
2
|
import { listDomains, sendEmail } from "../../api/generated/sdk.gen.js";
|
|
3
3
|
import { PrimitiveApiClient } from "../../api/index.js";
|
|
4
|
-
import { extractErrorCode, extractErrorPayload, formatErrorPayload, writeErrorWithHints, } from "../api-command.js";
|
|
4
|
+
import { extractErrorCode, extractErrorPayload, formatErrorPayload, runWithTiming, TIME_FLAG_DESCRIPTION, writeErrorWithHints, } from "../api-command.js";
|
|
5
5
|
// `primitive send` is the agent-grade shortcut for the most common
|
|
6
6
|
// case: send a fresh outbound email. It wraps `sending:send-email`
|
|
7
7
|
// with two ergonomic defaults that the underlying operation can't
|
|
@@ -139,43 +139,48 @@ class SendCommand extends Command {
|
|
|
139
139
|
"wait-timeout-ms": Flags.integer({
|
|
140
140
|
description: "Maximum time to wait when --wait is set. Defaults to 30000ms.",
|
|
141
141
|
}),
|
|
142
|
+
time: Flags.boolean({
|
|
143
|
+
description: TIME_FLAG_DESCRIPTION,
|
|
144
|
+
}),
|
|
142
145
|
};
|
|
143
146
|
async run() {
|
|
144
147
|
const { flags } = await this.parse(SendCommand);
|
|
145
148
|
if (!flags.body && !flags.html) {
|
|
146
149
|
throw new Errors.CLIError("Either --body or --html (or both) is required.");
|
|
147
150
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
151
|
+
await runWithTiming(flags.time, async () => {
|
|
152
|
+
const apiClient = new PrimitiveApiClient({
|
|
153
|
+
apiKey: flags["api-key"],
|
|
154
|
+
baseUrl: flags["base-url"],
|
|
155
|
+
});
|
|
156
|
+
const from = flags.from ?? (await pickDefaultFromAddress(apiClient));
|
|
157
|
+
const subject = flags.subject ?? (flags.body ? deriveSubject(flags.body) : "Message");
|
|
158
|
+
const result = await sendEmail({
|
|
159
|
+
body: {
|
|
160
|
+
from,
|
|
161
|
+
to: flags.to,
|
|
162
|
+
subject,
|
|
163
|
+
...(flags.body !== undefined ? { body_text: flags.body } : {}),
|
|
164
|
+
...(flags.html !== undefined ? { body_html: flags.html } : {}),
|
|
165
|
+
...(flags["in-reply-to"] !== undefined
|
|
166
|
+
? { in_reply_to: flags["in-reply-to"] }
|
|
167
|
+
: {}),
|
|
168
|
+
...(flags.wait !== undefined ? { wait: flags.wait } : {}),
|
|
169
|
+
...(flags["wait-timeout-ms"] !== undefined
|
|
170
|
+
? { wait_timeout_ms: flags["wait-timeout-ms"] }
|
|
171
|
+
: {}),
|
|
172
|
+
},
|
|
173
|
+
client: apiClient.client,
|
|
174
|
+
responseStyle: "fields",
|
|
175
|
+
});
|
|
176
|
+
if (result.error) {
|
|
177
|
+
writeErrorWithHints(extractErrorPayload(result.error));
|
|
178
|
+
process.exitCode = 1;
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const envelope = result.data;
|
|
182
|
+
this.log(JSON.stringify(envelope?.data ?? null, null, 2));
|
|
171
183
|
});
|
|
172
|
-
if (result.error) {
|
|
173
|
-
writeErrorWithHints(extractErrorPayload(result.error));
|
|
174
|
-
process.exitCode = 1;
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
const envelope = result.data;
|
|
178
|
-
this.log(JSON.stringify(envelope?.data ?? null, null, 2));
|
|
179
184
|
}
|
|
180
185
|
}
|
|
181
186
|
export default SendCommand;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command, Errors, Flags } from "@oclif/core";
|
|
2
2
|
import { getAccount } from "../../api/generated/sdk.gen.js";
|
|
3
3
|
import { PrimitiveApiClient } from "../../api/index.js";
|
|
4
|
-
import { extractErrorPayload, writeErrorWithHints } from "../api-command.js";
|
|
4
|
+
import { extractErrorPayload, runWithTiming, TIME_FLAG_DESCRIPTION, writeErrorWithHints, } from "../api-command.js";
|
|
5
5
|
// `primitive whoami` is the credentials smoke-test the AGX
|
|
6
6
|
// walkthrough kept asking for. Before this command, a user with a
|
|
7
7
|
// suspect API key had no fast way to verify "is my key live and
|
|
@@ -28,40 +28,45 @@ class WhoamiCommand extends Command {
|
|
|
28
28
|
description: "API base URL (defaults to PRIMITIVE_API_URL or production)",
|
|
29
29
|
env: "PRIMITIVE_API_URL",
|
|
30
30
|
}),
|
|
31
|
+
time: Flags.boolean({
|
|
32
|
+
description: TIME_FLAG_DESCRIPTION,
|
|
33
|
+
}),
|
|
31
34
|
};
|
|
32
35
|
async run() {
|
|
33
36
|
const { flags } = await this.parse(WhoamiCommand);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
await runWithTiming(flags.time, async () => {
|
|
38
|
+
const apiClient = new PrimitiveApiClient({
|
|
39
|
+
apiKey: flags["api-key"],
|
|
40
|
+
baseUrl: flags["base-url"],
|
|
41
|
+
});
|
|
42
|
+
const result = await getAccount({
|
|
43
|
+
client: apiClient.client,
|
|
44
|
+
responseStyle: "fields",
|
|
45
|
+
});
|
|
46
|
+
if (result.error) {
|
|
47
|
+
writeErrorWithHints(extractErrorPayload(result.error));
|
|
48
|
+
process.exitCode = 1;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const envelope = result.data;
|
|
52
|
+
const account = envelope?.data;
|
|
53
|
+
if (!account) {
|
|
54
|
+
process.stderr.write("Server returned an empty account body; this should not happen for a valid key.\n");
|
|
55
|
+
throw new Errors.CLIError("unexpected empty response");
|
|
56
|
+
}
|
|
57
|
+
// Concise human-readable summary on stderr; the full account
|
|
58
|
+
// JSON goes to stdout so a script can pipe it.
|
|
59
|
+
const onboarding = account.onboarding_completed === true
|
|
60
|
+
? "complete"
|
|
61
|
+
: account.onboarding_step
|
|
62
|
+
? `in progress (step: ${account.onboarding_step})`
|
|
63
|
+
: "incomplete";
|
|
64
|
+
process.stderr.write(`Authenticated as ${account.email}\n`);
|
|
65
|
+
process.stderr.write(` Account id: ${account.id}\n`);
|
|
66
|
+
process.stderr.write(` Plan: ${account.plan}\n`);
|
|
67
|
+
process.stderr.write(` Onboarding: ${onboarding}\n`);
|
|
68
|
+
this.log(JSON.stringify(account, null, 2));
|
|
41
69
|
});
|
|
42
|
-
if (result.error) {
|
|
43
|
-
writeErrorWithHints(extractErrorPayload(result.error));
|
|
44
|
-
process.exitCode = 1;
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
const envelope = result.data;
|
|
48
|
-
const account = envelope?.data;
|
|
49
|
-
if (!account) {
|
|
50
|
-
process.stderr.write("Server returned an empty account body; this should not happen for a valid key.\n");
|
|
51
|
-
throw new Errors.CLIError("unexpected empty response");
|
|
52
|
-
}
|
|
53
|
-
// Concise human-readable summary on stderr; the full account
|
|
54
|
-
// JSON goes to stdout so a script can pipe it.
|
|
55
|
-
const onboarding = account.onboarding_completed === true
|
|
56
|
-
? "complete"
|
|
57
|
-
: account.onboarding_step
|
|
58
|
-
? `in progress (step: ${account.onboarding_step})`
|
|
59
|
-
: "incomplete";
|
|
60
|
-
process.stderr.write(`Authenticated as ${account.email}\n`);
|
|
61
|
-
process.stderr.write(` Account id: ${account.id}\n`);
|
|
62
|
-
process.stderr.write(` Plan: ${account.plan}\n`);
|
|
63
|
-
process.stderr.write(` Onboarding: ${onboarding}\n`);
|
|
64
|
-
this.log(JSON.stringify(account, null, 2));
|
|
65
70
|
}
|
|
66
71
|
}
|
|
67
72
|
export default WhoamiCommand;
|
|
@@ -9,7 +9,7 @@ export const openapiDocument = {
|
|
|
9
9
|
"info": {
|
|
10
10
|
"title": "Primitive API",
|
|
11
11
|
"version": "1.0.0",
|
|
12
|
-
"description": "The Primitive API lets you manage domains, emails, webhook endpoints,\nfilters, and account settings programmatically.\n\n## Authentication\n\nAll endpoints require a Bearer token in the `Authorization` header:\n\n```\nAuthorization: Bearer prim_<your_api_key>\n```\n\nAPI keys are org-scoped. Create and manage them in your dashboard\nunder Settings > API Keys.\n\n## Rate Limiting\n\nThe API enforces a sliding window rate limit of **120 requests per\n60 seconds** per organization. When exceeded, the API returns `429`\nwith a `Retry-After` header indicating how many seconds to wait.\n\n## Pagination\n\nList endpoints use cursor-based pagination. Responses include a\n`meta` object with `total`, `limit`, and `cursor` fields. Pass the\n`cursor` value as a query parameter to fetch the next page. When\n`cursor` is `null`, there are no more results.\n\n## Response Format\n\nAll responses use a consistent envelope:\n\n```json\n{\n \"success\": true,\n \"data\": { ... },\n \"meta\": { \"total\": 42, \"limit\": 50, \"cursor\": \"...\" }\n}\n```\n\nErrors follow the same pattern:\n\n```json\n{\n \"success\": false,\n \"error\": { \"code\": \"not_found\", \"message\": \"Email not found\" }\n}\n```\n",
|
|
12
|
+
"description": "The Primitive API lets you manage domains, emails, webhook endpoints,\nfilters, and account settings programmatically.\n\n## Authentication\n\nAll endpoints require a Bearer token in the `Authorization` header:\n\n```\nAuthorization: Bearer prim_<your_api_key>\n```\n\nAPI keys are org-scoped. Create and manage them in your dashboard\nunder Settings > API Keys.\n\n## Rate Limiting\n\nThe API enforces a sliding window rate limit of **120 requests per\n60 seconds** per organization. When exceeded, the API returns `429`\nwith a `Retry-After` header indicating how many seconds to wait.\n\n## Pagination\n\nList endpoints use cursor-based pagination. Responses include a\n`meta` object with `total`, `limit`, and `cursor` fields. Pass the\n`cursor` value as a query parameter to fetch the next page. When\n`cursor` is `null`, there are no more results.\n\n## Response Format\n\nAll responses use a consistent envelope:\n\n```json\n{\n \"success\": true,\n \"data\": { ... },\n \"meta\": { \"total\": 42, \"limit\": 50, \"cursor\": \"...\" }\n}\n```\n\nErrors follow the same pattern:\n\n```json\n{\n \"success\": false,\n \"error\": { \"code\": \"not_found\", \"message\": \"Email not found\" }\n}\n```\n\n## Webhook signing\n\nOutbound webhook deliveries (configured via the `endpoints` API)\nare signed so receivers can verify they came from Primitive and\nhave not been tampered with in transit. The signing scheme is\ndeliberately simple so it can be reimplemented in any language\nin a few lines. The Node SDK's `verifyWebhookSignature` helper\nis the reference implementation; the wire details below let you\nwrite a verifier in Python, Go, Ruby, etc. without reading our\nsource.\n\n**Header**: `Primitive-Signature: t=<unix-seconds>,v1=<hex>`\n\nA legacy `MyMX-Signature` header is also sent on every delivery\nwith the same value, retained for back-compatibility with\nintegrations written before the rename. New code should read\n`Primitive-Signature`.\n\n**Signed string**: `${timestamp}.${rawBody}` where `timestamp`\nis the Unix-seconds integer from the `t=` parameter and\n`rawBody` is the exact bytes of the HTTP request body BEFORE\nany JSON decoding. Verify against the raw body, not a\nre-serialized parse, or you will silently mismatch on\ninsignificant whitespace.\n\n**Signature**: HMAC-SHA256 of the signed string, hex-encoded\n(lowercase). Use the account's webhook secret as the HMAC key,\nas a UTF-8 byte sequence.\n\n**Secret**: returned by `GET /account/webhook-secret`. The\nstring looks base64-shaped (e.g. `XNHBBW8VqoBjRfNs1tkZj11jTk...`)\nbut is NOT base64; use it AS-IS as a UTF-8 string for the HMAC\nkey. Base64-decoding before HMAC will silently produce\nmismatched signatures.\n\n**Tolerance**: by convention, reject deliveries whose `t=`\ntimestamp is more than 5 minutes off your wall-clock to defend\nagainst replay attacks. The Node SDK's helper enforces this by\ndefault.\n\n**Verification recipe** (any language):\n\n```\n1. Read the raw HTTP body (do not parse).\n2. Read `Primitive-Signature: t=<ts>,v1=<sig>`.\n3. Reject if abs(now - ts) > 300 seconds.\n4. expected = HMAC_SHA256_hex(secret_utf8, f\"{ts}.{rawBody}\")\n5. Constant-time compare expected to sig. Reject if not equal.\n```\n\nFor Node, use `verifyWebhookSignature` from\n`@primitivedotdev/sdk/webhook` (or the higher-level\n`handleWebhook` helper if you want a one-liner). For other\nlanguages, the recipe above is everything you need.\n\nTest deliveries: `POST /endpoints/{id}/test` triggers a fake\ndelivery to your endpoint URL, signed with your real account\nsecret, so you can confirm verification end-to-end without\nneeding real inbound mail. The test response carries the exact\n`signature` header value sent on the wire so you can compare\nstrings directly.\n",
|
|
13
13
|
"contact": {
|
|
14
14
|
"name": "Primitive",
|
|
15
15
|
"url": "https://primitive.dev"
|
|
@@ -193,7 +193,7 @@ export const openapiDocument = {
|
|
|
193
193
|
"get": {
|
|
194
194
|
"operationId": "getWebhookSecret",
|
|
195
195
|
"summary": "Get webhook signing secret",
|
|
196
|
-
"description": "Returns the webhook signing secret for your account. If no
|
|
196
|
+
"description": "Returns the webhook signing secret for your account. If no\nsecret exists yet, one is generated automatically on first\naccess.\n\nSigning is account-scoped, not per-endpoint. Every webhook\ndelivery from any of your registered endpoints is signed\nwith this single secret. Rotate via\n`POST /account/webhook-secret/rotate`.\n\n**Secret format**: the returned string looks base64-shaped\n(e.g. `XNHBBW8VqoBjRfNs1tkZj11jTk...`) but is NOT base64.\nUse it AS-IS as a UTF-8 string when computing HMAC over a\ndelivery body. Base64-decoding before HMAC will silently\nproduce mismatched signatures.\n\nSee the API-level \"Webhook signing\" section for the full\nwire format (header name, signed string shape, hash algo,\ntolerance) including a language-agnostic verification\nrecipe.\n",
|
|
197
197
|
"tags": [
|
|
198
198
|
"Account"
|
|
199
199
|
],
|
|
@@ -533,7 +533,7 @@ export const openapiDocument = {
|
|
|
533
533
|
"get": {
|
|
534
534
|
"operationId": "listEmails",
|
|
535
535
|
"summary": "List inbound emails",
|
|
536
|
-
"description": "Returns a paginated list of INBOUND emails received at your\nverified domains. Outbound messages sent via /send-mail are
|
|
536
|
+
"description": "Returns a paginated list of INBOUND emails received at your\nverified domains. Outbound messages sent via /send-mail are\nnot included; this endpoint is the inbox view, not a\nunified send/receive history.\n\nSupports filtering by domain, status, date range, and\nfree-text search across subject, sender, and recipient\nfields.\n\nFor a compact text-table summary of the most recent N\ninbounds (no filters, no cursor pagination), the CLI ships\n`primitive emails:latest` as a one-line-per-email shortcut.\nIt's TTY-aware so id columns are full UUIDs when piped, and\na `--json` flag returns the same envelope this endpoint\ndoes. Use whichever fits the call site.\n",
|
|
537
537
|
"tags": [
|
|
538
538
|
"Emails"
|
|
539
539
|
],
|
|
@@ -1080,7 +1080,7 @@ export const openapiDocument = {
|
|
|
1080
1080
|
"post": {
|
|
1081
1081
|
"operationId": "createEndpoint",
|
|
1082
1082
|
"summary": "Create a webhook endpoint",
|
|
1083
|
-
"description": "Creates a new webhook endpoint. If a deactivated endpoint
|
|
1083
|
+
"description": "Creates a new webhook endpoint. If a deactivated endpoint\nwith the same URL and domain exists, it is reactivated\ninstead. Subject to plan limits on the number of active\nendpoints.\n\n**Signing is account-scoped, not per-endpoint.** This call\ndoes not return any signing material; every endpoint on the\naccount uses the same webhook secret, fetched via\n`GET /account/webhook-secret`. See the API-level \"Webhook\nsigning\" section for the full wire format (header name,\nsigned string, hash algo, secret format, tolerance) and a\nlanguage-agnostic verification recipe.\n\nAfter creating the endpoint, fire a test delivery against\nit via `POST /endpoints/{id}/test` to confirm your verifier\naccepts the signature.\n",
|
|
1084
1084
|
"tags": [
|
|
1085
1085
|
"Endpoints"
|
|
1086
1086
|
],
|
|
@@ -152,7 +152,7 @@ export const operationManifest = [
|
|
|
152
152
|
"binaryResponse": false,
|
|
153
153
|
"bodyRequired": false,
|
|
154
154
|
"command": "get-webhook-secret",
|
|
155
|
-
"description": "Returns the webhook signing secret for your account. If no
|
|
155
|
+
"description": "Returns the webhook signing secret for your account. If no\nsecret exists yet, one is generated automatically on first\naccess.\n\nSigning is account-scoped, not per-endpoint. Every webhook\ndelivery from any of your registered endpoints is signed\nwith this single secret. Rotate via\n`POST /account/webhook-secret/rotate`.\n\n**Secret format**: the returned string looks base64-shaped\n(e.g. `XNHBBW8VqoBjRfNs1tkZj11jTk...`) but is NOT base64.\nUse it AS-IS as a UTF-8 string when computing HMAC over a\ndelivery body. Base64-decoding before HMAC will silently\nproduce mismatched signatures.\n\nSee the API-level \"Webhook signing\" section for the full\nwire format (header name, signed string shape, hash algo,\ntolerance) including a language-agnostic verification\nrecipe.\n",
|
|
156
156
|
"hasJsonBody": false,
|
|
157
157
|
"method": "GET",
|
|
158
158
|
"operationId": "getWebhookSecret",
|
|
@@ -1071,7 +1071,7 @@ export const operationManifest = [
|
|
|
1071
1071
|
"binaryResponse": false,
|
|
1072
1072
|
"bodyRequired": false,
|
|
1073
1073
|
"command": "list-emails",
|
|
1074
|
-
"description": "Returns a paginated list of INBOUND emails received at your\nverified domains. Outbound messages sent via /send-mail are
|
|
1074
|
+
"description": "Returns a paginated list of INBOUND emails received at your\nverified domains. Outbound messages sent via /send-mail are\nnot included; this endpoint is the inbox view, not a\nunified send/receive history.\n\nSupports filtering by domain, status, date range, and\nfree-text search across subject, sender, and recipient\nfields.\n\nFor a compact text-table summary of the most recent N\ninbounds (no filters, no cursor pagination), the CLI ships\n`primitive emails:latest` as a one-line-per-email shortcut.\nIt's TTY-aware so id columns are full UUIDs when piped, and\na `--json` flag returns the same envelope this endpoint\ndoes. Use whichever fits the call site.\n",
|
|
1075
1075
|
"hasJsonBody": false,
|
|
1076
1076
|
"method": "GET",
|
|
1077
1077
|
"operationId": "listEmails",
|
|
@@ -1286,7 +1286,7 @@ export const operationManifest = [
|
|
|
1286
1286
|
"binaryResponse": false,
|
|
1287
1287
|
"bodyRequired": true,
|
|
1288
1288
|
"command": "create-endpoint",
|
|
1289
|
-
"description": "Creates a new webhook endpoint. If a deactivated endpoint
|
|
1289
|
+
"description": "Creates a new webhook endpoint. If a deactivated endpoint\nwith the same URL and domain exists, it is reactivated\ninstead. Subject to plan limits on the number of active\nendpoints.\n\n**Signing is account-scoped, not per-endpoint.** This call\ndoes not return any signing material; every endpoint on the\naccount uses the same webhook secret, fetched via\n`GET /account/webhook-secret`. See the API-level \"Webhook\nsigning\" section for the full wire format (header name,\nsigned string, hash algo, secret format, tolerance) and a\nlanguage-agnostic verification recipe.\n\nAfter creating the endpoint, fire a test delivery against\nit via `POST /endpoints/{id}/test` to confirm your verifier\naccepts the signature.\n",
|
|
1290
1290
|
"hasJsonBody": true,
|
|
1291
1291
|
"method": "POST",
|
|
1292
1292
|
"operationId": "createEndpoint",
|