@primitivedotdev/sdk 0.26.1 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +2 -2
  2. package/dist/api/index.js +3 -406
  3. package/dist/openapi/index.js +7631 -7
  4. package/package.json +6 -63
  5. package/bin/run.js +0 -20
  6. package/dist/api/generated/client/client.gen.js +0 -235
  7. package/dist/api/generated/client/index.js +0 -6
  8. package/dist/api/generated/client/types.gen.js +0 -2
  9. package/dist/api/generated/client/utils.gen.js +0 -228
  10. package/dist/api/generated/client.gen.js +0 -3
  11. package/dist/api/generated/core/auth.gen.js +0 -14
  12. package/dist/api/generated/core/bodySerializer.gen.js +0 -57
  13. package/dist/api/generated/core/params.gen.js +0 -100
  14. package/dist/api/generated/core/pathSerializer.gen.js +0 -106
  15. package/dist/api/generated/core/queryKeySerializer.gen.js +0 -92
  16. package/dist/api/generated/core/serverSentEvents.gen.js +0 -132
  17. package/dist/api/generated/core/types.gen.js +0 -2
  18. package/dist/api/generated/core/utils.gen.js +0 -87
  19. package/dist/api/generated/index.js +0 -2
  20. package/dist/api/generated/sdk.gen.js +0 -878
  21. package/dist/api/generated/types.gen.js +0 -2
  22. package/dist/api/verify-signature.js +0 -198
  23. package/dist/oclif/api-command.js +0 -755
  24. package/dist/oclif/auth.js +0 -223
  25. package/dist/oclif/commands/emails-latest.js +0 -185
  26. package/dist/oclif/commands/emails-poll.js +0 -121
  27. package/dist/oclif/commands/emails-wait.js +0 -171
  28. package/dist/oclif/commands/emails-watch.js +0 -165
  29. package/dist/oclif/commands/functions-deploy.js +0 -124
  30. package/dist/oclif/commands/functions-init.js +0 -256
  31. package/dist/oclif/commands/functions-redeploy.js +0 -113
  32. package/dist/oclif/commands/functions-set-secret.js +0 -213
  33. package/dist/oclif/commands/login.js +0 -237
  34. package/dist/oclif/commands/logout.js +0 -88
  35. package/dist/oclif/commands/send.js +0 -222
  36. package/dist/oclif/commands/whoami.js +0 -95
  37. package/dist/oclif/fish-completion.js +0 -87
  38. package/dist/oclif/index.js +0 -167
  39. package/dist/oclif/lint/raw-send-mail-fetch.js +0 -98
  40. package/dist/openapi/openapi.generated.js +0 -5754
  41. package/dist/openapi/operations.generated.js +0 -4626
  42. package/dist/parser/address-parser.js +0 -129
  43. package/dist/types.generated.js +0 -7
  44. package/dist/types.js +0 -53
  45. package/dist/webhook/errors.js +0 -224
  46. package/dist/webhook/received-email.js +0 -82
  47. package/oclif.manifest.json +0 -4380
@@ -1,4626 +0,0 @@
1
- /**
2
- * Generated operation metadata for the Primitive API CLI and SDK tooling.
3
- *
4
- * AUTO-GENERATED - DO NOT EDIT
5
- * Run `pnpm generate:openapi` to regenerate.
6
- */
7
- export const operationManifest = [
8
- {
9
- "binaryResponse": false,
10
- "bodyRequired": false,
11
- "command": "get-account",
12
- "description": null,
13
- "hasJsonBody": false,
14
- "method": "GET",
15
- "operationId": "getAccount",
16
- "path": "/account",
17
- "pathParams": [],
18
- "queryParams": [],
19
- "requestSchema": null,
20
- "responseSchema": {
21
- "type": "object",
22
- "properties": {
23
- "id": {
24
- "type": "string",
25
- "format": "uuid"
26
- },
27
- "email": {
28
- "type": "string"
29
- },
30
- "plan": {
31
- "type": "string"
32
- },
33
- "created_at": {
34
- "type": "string",
35
- "format": "date-time"
36
- },
37
- "onboarding_completed": {
38
- "type": "boolean"
39
- },
40
- "onboarding_step": {
41
- "type": [
42
- "string",
43
- "null"
44
- ]
45
- },
46
- "stripe_subscription_status": {
47
- "type": [
48
- "string",
49
- "null"
50
- ]
51
- },
52
- "subscription_current_period_end": {
53
- "type": [
54
- "string",
55
- "null"
56
- ],
57
- "format": "date-time"
58
- },
59
- "subscription_cancel_at_period_end": {
60
- "type": [
61
- "boolean",
62
- "null"
63
- ]
64
- },
65
- "spam_threshold": {
66
- "type": [
67
- "number",
68
- "null"
69
- ],
70
- "minimum": 0,
71
- "maximum": 15
72
- },
73
- "discard_content_on_webhook_confirmed": {
74
- "type": "boolean"
75
- },
76
- "webhook_secret_rotated_at": {
77
- "type": [
78
- "string",
79
- "null"
80
- ],
81
- "format": "date-time"
82
- }
83
- },
84
- "required": [
85
- "id",
86
- "email",
87
- "plan",
88
- "created_at",
89
- "discard_content_on_webhook_confirmed"
90
- ]
91
- },
92
- "sdkName": "getAccount",
93
- "summary": "Get account info",
94
- "tag": "Account",
95
- "tagCommand": "account"
96
- },
97
- {
98
- "binaryResponse": false,
99
- "bodyRequired": false,
100
- "command": "get-storage-stats",
101
- "description": null,
102
- "hasJsonBody": false,
103
- "method": "GET",
104
- "operationId": "getStorageStats",
105
- "path": "/account/storage",
106
- "pathParams": [],
107
- "queryParams": [],
108
- "requestSchema": null,
109
- "responseSchema": {
110
- "type": "object",
111
- "properties": {
112
- "used_bytes": {
113
- "type": "integer",
114
- "description": "Total storage used in bytes"
115
- },
116
- "used_kb": {
117
- "type": "number",
118
- "description": "Total storage used in kilobytes (1 decimal)"
119
- },
120
- "used_mb": {
121
- "type": "number",
122
- "description": "Total storage used in megabytes (2 decimals)"
123
- },
124
- "quota_mb": {
125
- "type": "number",
126
- "description": "Storage quota in megabytes (based on plan)"
127
- },
128
- "percentage": {
129
- "type": "number",
130
- "description": "Percentage of quota used (1 decimal)"
131
- },
132
- "emails_count": {
133
- "type": "integer",
134
- "description": "Number of stored emails"
135
- }
136
- },
137
- "required": [
138
- "used_bytes",
139
- "used_kb",
140
- "used_mb",
141
- "quota_mb",
142
- "percentage",
143
- "emails_count"
144
- ]
145
- },
146
- "sdkName": "getStorageStats",
147
- "summary": "Get storage usage",
148
- "tag": "Account",
149
- "tagCommand": "account"
150
- },
151
- {
152
- "binaryResponse": false,
153
- "bodyRequired": false,
154
- "command": "get-webhook-secret",
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
- "hasJsonBody": false,
157
- "method": "GET",
158
- "operationId": "getWebhookSecret",
159
- "path": "/account/webhook-secret",
160
- "pathParams": [],
161
- "queryParams": [],
162
- "requestSchema": null,
163
- "responseSchema": {
164
- "type": "object",
165
- "properties": {
166
- "secret": {
167
- "type": "string",
168
- "description": "The webhook signing secret value"
169
- }
170
- },
171
- "required": [
172
- "secret"
173
- ]
174
- },
175
- "sdkName": "getWebhookSecret",
176
- "summary": "Get webhook signing secret",
177
- "tag": "Account",
178
- "tagCommand": "account"
179
- },
180
- {
181
- "binaryResponse": false,
182
- "bodyRequired": false,
183
- "command": "rotate-webhook-secret",
184
- "description": "Generates a new webhook signing secret, replacing the current one.\nRate limited to once per 60 minutes.\n",
185
- "hasJsonBody": false,
186
- "method": "POST",
187
- "operationId": "rotateWebhookSecret",
188
- "path": "/account/webhook-secret/rotate",
189
- "pathParams": [],
190
- "queryParams": [],
191
- "requestSchema": null,
192
- "responseSchema": {
193
- "type": "object",
194
- "properties": {
195
- "secret": {
196
- "type": "string",
197
- "description": "The webhook signing secret value"
198
- }
199
- },
200
- "required": [
201
- "secret"
202
- ]
203
- },
204
- "sdkName": "rotateWebhookSecret",
205
- "summary": "Rotate webhook signing secret",
206
- "tag": "Account",
207
- "tagCommand": "account"
208
- },
209
- {
210
- "binaryResponse": false,
211
- "bodyRequired": true,
212
- "command": "update-account",
213
- "description": null,
214
- "hasJsonBody": true,
215
- "method": "PATCH",
216
- "operationId": "updateAccount",
217
- "path": "/account",
218
- "pathParams": [],
219
- "queryParams": [],
220
- "requestSchema": {
221
- "type": "object",
222
- "additionalProperties": false,
223
- "properties": {
224
- "spam_threshold": {
225
- "type": [
226
- "number",
227
- "null"
228
- ],
229
- "minimum": 0,
230
- "maximum": 15,
231
- "description": "Global spam score threshold (0-15). Emails scoring above this are rejected. Set to null to disable."
232
- },
233
- "discard_content_on_webhook_confirmed": {
234
- "type": "boolean",
235
- "description": "Whether to discard email content after the webhook endpoint confirms receipt."
236
- }
237
- },
238
- "minProperties": 1
239
- },
240
- "responseSchema": {
241
- "type": "object",
242
- "properties": {
243
- "id": {
244
- "type": "string",
245
- "format": "uuid"
246
- },
247
- "email": {
248
- "type": "string"
249
- },
250
- "plan": {
251
- "type": "string"
252
- },
253
- "spam_threshold": {
254
- "type": [
255
- "number",
256
- "null"
257
- ],
258
- "minimum": 0,
259
- "maximum": 15
260
- },
261
- "discard_content_on_webhook_confirmed": {
262
- "type": "boolean"
263
- }
264
- },
265
- "required": [
266
- "id",
267
- "email",
268
- "plan",
269
- "discard_content_on_webhook_confirmed"
270
- ]
271
- },
272
- "sdkName": "updateAccount",
273
- "summary": "Update account settings",
274
- "tag": "Account",
275
- "tagCommand": "account"
276
- },
277
- {
278
- "binaryResponse": false,
279
- "bodyRequired": false,
280
- "command": "cli-logout",
281
- "description": "Revokes the API key used to authenticate the request. CLI clients use\nthis endpoint during `primitive logout` before removing local credentials.\n",
282
- "hasJsonBody": true,
283
- "method": "POST",
284
- "operationId": "cliLogout",
285
- "path": "/cli/logout",
286
- "pathParams": [],
287
- "queryParams": [],
288
- "requestSchema": {
289
- "type": "object",
290
- "additionalProperties": false,
291
- "properties": {
292
- "key_id": {
293
- "type": "string",
294
- "format": "uuid",
295
- "description": "Optional key id guard; when provided it must match the authenticated API key"
296
- }
297
- }
298
- },
299
- "responseSchema": {
300
- "type": "object",
301
- "properties": {
302
- "revoked": {
303
- "type": "boolean",
304
- "const": true
305
- },
306
- "key_id": {
307
- "type": "string",
308
- "format": "uuid"
309
- }
310
- },
311
- "required": [
312
- "revoked",
313
- "key_id"
314
- ]
315
- },
316
- "sdkName": "cliLogout",
317
- "summary": "Revoke the current CLI API key",
318
- "tag": "CLI",
319
- "tagCommand": "cli"
320
- },
321
- {
322
- "binaryResponse": false,
323
- "bodyRequired": true,
324
- "command": "poll-cli-login",
325
- "description": "Polls a CLI login session until the browser approval either succeeds,\nis denied, expires, or is polled too quickly. The API key is generated\nonly after approval and is returned exactly once.\n",
326
- "hasJsonBody": true,
327
- "method": "POST",
328
- "operationId": "pollCliLogin",
329
- "path": "/cli/login/poll",
330
- "pathParams": [],
331
- "queryParams": [],
332
- "requestSchema": {
333
- "type": "object",
334
- "additionalProperties": false,
335
- "properties": {
336
- "device_code": {
337
- "type": "string",
338
- "minLength": 1
339
- }
340
- },
341
- "required": [
342
- "device_code"
343
- ]
344
- },
345
- "responseSchema": {
346
- "type": "object",
347
- "properties": {
348
- "api_key": {
349
- "type": "string",
350
- "description": "Newly-created API key for CLI authentication"
351
- },
352
- "key_id": {
353
- "type": "string",
354
- "format": "uuid"
355
- },
356
- "key_prefix": {
357
- "type": "string"
358
- },
359
- "org_id": {
360
- "type": "string",
361
- "format": "uuid"
362
- },
363
- "org_name": {
364
- "type": [
365
- "string",
366
- "null"
367
- ]
368
- }
369
- },
370
- "required": [
371
- "api_key",
372
- "key_id",
373
- "key_prefix",
374
- "org_id",
375
- "org_name"
376
- ]
377
- },
378
- "sdkName": "pollCliLogin",
379
- "summary": "Poll CLI browser login",
380
- "tag": "CLI",
381
- "tagCommand": "cli"
382
- },
383
- {
384
- "binaryResponse": false,
385
- "bodyRequired": false,
386
- "command": "start-cli-login",
387
- "description": "Starts a browser-assisted CLI login session. The response includes a\ndevice code for polling and a user code that the user approves in the\nbrowser. This endpoint does not require an API key.\n",
388
- "hasJsonBody": true,
389
- "method": "POST",
390
- "operationId": "startCliLogin",
391
- "path": "/cli/login/start",
392
- "pathParams": [],
393
- "queryParams": [],
394
- "requestSchema": {
395
- "type": "object",
396
- "additionalProperties": false,
397
- "properties": {
398
- "device_name": {
399
- "type": "string",
400
- "minLength": 1,
401
- "maxLength": 80,
402
- "description": "Human-readable device name shown during browser approval"
403
- },
404
- "metadata": {
405
- "type": "object",
406
- "additionalProperties": true,
407
- "description": "Optional client metadata stored with the login session; serialized JSON must be 2048 bytes or fewer"
408
- }
409
- }
410
- },
411
- "responseSchema": {
412
- "type": "object",
413
- "properties": {
414
- "device_code": {
415
- "type": "string",
416
- "description": "Opaque code used by the CLI to poll for approval"
417
- },
418
- "user_code": {
419
- "type": "string",
420
- "pattern": "^[BCDFGHJKLMNPQRSTVWXZ]{4}-[BCDFGHJKLMNPQRSTVWXZ]{4}$",
421
- "description": "Short code the user confirms in the browser"
422
- },
423
- "verification_uri": {
424
- "type": "string",
425
- "description": "Browser URL where the user approves the login"
426
- },
427
- "verification_uri_complete": {
428
- "type": "string",
429
- "description": "Browser URL with the user code prefilled"
430
- },
431
- "expires_in": {
432
- "type": "integer",
433
- "description": "Seconds until the login session expires"
434
- },
435
- "interval": {
436
- "type": "integer",
437
- "description": "Minimum seconds between poll requests"
438
- }
439
- },
440
- "required": [
441
- "device_code",
442
- "user_code",
443
- "verification_uri",
444
- "verification_uri_complete",
445
- "expires_in",
446
- "interval"
447
- ]
448
- },
449
- "sdkName": "startCliLogin",
450
- "summary": "Start CLI browser login",
451
- "tag": "CLI",
452
- "tagCommand": "cli"
453
- },
454
- {
455
- "binaryResponse": false,
456
- "bodyRequired": true,
457
- "command": "add-domain",
458
- "description": "Creates an unverified domain claim. You will receive a\n`verification_token` to add as a DNS TXT record before\ncalling the verify endpoint.\n",
459
- "hasJsonBody": true,
460
- "method": "POST",
461
- "operationId": "addDomain",
462
- "path": "/domains",
463
- "pathParams": [],
464
- "queryParams": [],
465
- "requestSchema": {
466
- "type": "object",
467
- "additionalProperties": false,
468
- "properties": {
469
- "domain": {
470
- "type": "string",
471
- "minLength": 1,
472
- "maxLength": 253,
473
- "description": "The domain name to claim (e.g. \"example.com\")"
474
- }
475
- },
476
- "required": [
477
- "domain"
478
- ]
479
- },
480
- "responseSchema": {
481
- "type": "object",
482
- "properties": {
483
- "id": {
484
- "type": "string",
485
- "format": "uuid"
486
- },
487
- "org_id": {
488
- "type": "string",
489
- "format": "uuid"
490
- },
491
- "domain": {
492
- "type": "string"
493
- },
494
- "verified": {
495
- "type": "boolean",
496
- "const": false
497
- },
498
- "verification_token": {
499
- "type": "string",
500
- "description": "Add this value as a TXT record to verify ownership"
501
- },
502
- "created_at": {
503
- "type": "string",
504
- "format": "date-time"
505
- }
506
- },
507
- "required": [
508
- "id",
509
- "org_id",
510
- "domain",
511
- "verified",
512
- "verification_token",
513
- "created_at"
514
- ]
515
- },
516
- "sdkName": "addDomain",
517
- "summary": "Claim a new domain",
518
- "tag": "Domains",
519
- "tagCommand": "domains"
520
- },
521
- {
522
- "binaryResponse": false,
523
- "bodyRequired": false,
524
- "command": "delete-domain",
525
- "description": "Deletes a verified or unverified domain claim.",
526
- "hasJsonBody": false,
527
- "method": "DELETE",
528
- "operationId": "deleteDomain",
529
- "path": "/domains/{id}",
530
- "pathParams": [
531
- {
532
- "description": "Resource UUID",
533
- "enum": null,
534
- "name": "id",
535
- "required": true,
536
- "type": "string"
537
- }
538
- ],
539
- "queryParams": [],
540
- "requestSchema": null,
541
- "responseSchema": null,
542
- "sdkName": "deleteDomain",
543
- "summary": "Delete a domain",
544
- "tag": "Domains",
545
- "tagCommand": "domains"
546
- },
547
- {
548
- "binaryResponse": false,
549
- "bodyRequired": false,
550
- "command": "list-domains",
551
- "description": "Returns all verified and unverified domains for your organization,\nsorted by creation date (newest first). Each domain includes a\n`verified` boolean to distinguish between the two states.\n",
552
- "hasJsonBody": false,
553
- "method": "GET",
554
- "operationId": "listDomains",
555
- "path": "/domains",
556
- "pathParams": [],
557
- "queryParams": [],
558
- "requestSchema": null,
559
- "responseSchema": {
560
- "type": "array",
561
- "items": {
562
- "description": "A domain can be either verified or unverified. Verified domains have\n`is_active` and `spam_threshold` fields. Unverified domains have a\n`verification_token` for DNS verification.\n",
563
- "oneOf": [
564
- {
565
- "type": "object",
566
- "properties": {
567
- "id": {
568
- "type": "string",
569
- "format": "uuid"
570
- },
571
- "org_id": {
572
- "type": "string",
573
- "format": "uuid"
574
- },
575
- "domain": {
576
- "type": "string"
577
- },
578
- "verified": {
579
- "type": "boolean",
580
- "const": true
581
- },
582
- "is_active": {
583
- "type": "boolean"
584
- },
585
- "spam_threshold": {
586
- "type": [
587
- "number",
588
- "null"
589
- ],
590
- "minimum": 0,
591
- "maximum": 15
592
- },
593
- "verification_token": {
594
- "type": [
595
- "string",
596
- "null"
597
- ]
598
- },
599
- "created_at": {
600
- "type": "string",
601
- "format": "date-time"
602
- }
603
- },
604
- "required": [
605
- "id",
606
- "org_id",
607
- "domain",
608
- "verified",
609
- "is_active",
610
- "created_at"
611
- ]
612
- },
613
- {
614
- "type": "object",
615
- "properties": {
616
- "id": {
617
- "type": "string",
618
- "format": "uuid"
619
- },
620
- "org_id": {
621
- "type": "string",
622
- "format": "uuid"
623
- },
624
- "domain": {
625
- "type": "string"
626
- },
627
- "verified": {
628
- "type": "boolean",
629
- "const": false
630
- },
631
- "verification_token": {
632
- "type": "string",
633
- "description": "Add this value as a TXT record to verify ownership"
634
- },
635
- "created_at": {
636
- "type": "string",
637
- "format": "date-time"
638
- }
639
- },
640
- "required": [
641
- "id",
642
- "org_id",
643
- "domain",
644
- "verified",
645
- "verification_token",
646
- "created_at"
647
- ]
648
- }
649
- ]
650
- }
651
- },
652
- "sdkName": "listDomains",
653
- "summary": "List all domains",
654
- "tag": "Domains",
655
- "tagCommand": "domains"
656
- },
657
- {
658
- "binaryResponse": false,
659
- "bodyRequired": true,
660
- "command": "update-domain",
661
- "description": "Update a verified domain's settings. Only verified domains can be\nupdated. Per-domain spam thresholds require a Pro plan.\n",
662
- "hasJsonBody": true,
663
- "method": "PATCH",
664
- "operationId": "updateDomain",
665
- "path": "/domains/{id}",
666
- "pathParams": [
667
- {
668
- "description": "Resource UUID",
669
- "enum": null,
670
- "name": "id",
671
- "required": true,
672
- "type": "string"
673
- }
674
- ],
675
- "queryParams": [],
676
- "requestSchema": {
677
- "type": "object",
678
- "additionalProperties": false,
679
- "properties": {
680
- "is_active": {
681
- "type": "boolean",
682
- "description": "Whether the domain accepts incoming emails"
683
- },
684
- "spam_threshold": {
685
- "type": [
686
- "number",
687
- "null"
688
- ],
689
- "minimum": 0,
690
- "maximum": 15,
691
- "description": "Per-domain spam threshold override (Pro plan required)"
692
- }
693
- },
694
- "minProperties": 1
695
- },
696
- "responseSchema": {
697
- "type": "object",
698
- "properties": {
699
- "id": {
700
- "type": "string",
701
- "format": "uuid"
702
- },
703
- "org_id": {
704
- "type": "string",
705
- "format": "uuid"
706
- },
707
- "domain": {
708
- "type": "string"
709
- },
710
- "verified": {
711
- "type": "boolean",
712
- "const": true
713
- },
714
- "is_active": {
715
- "type": "boolean"
716
- },
717
- "spam_threshold": {
718
- "type": [
719
- "number",
720
- "null"
721
- ],
722
- "minimum": 0,
723
- "maximum": 15
724
- },
725
- "verification_token": {
726
- "type": [
727
- "string",
728
- "null"
729
- ]
730
- },
731
- "created_at": {
732
- "type": "string",
733
- "format": "date-time"
734
- }
735
- },
736
- "required": [
737
- "id",
738
- "org_id",
739
- "domain",
740
- "verified",
741
- "is_active",
742
- "created_at"
743
- ]
744
- },
745
- "sdkName": "updateDomain",
746
- "summary": "Update domain settings",
747
- "tag": "Domains",
748
- "tagCommand": "domains"
749
- },
750
- {
751
- "binaryResponse": false,
752
- "bodyRequired": false,
753
- "command": "verify-domain",
754
- "description": "Checks DNS records (MX and TXT) to verify domain ownership.\nOn success, the domain is promoted from unverified to verified.\nOn failure, returns which checks passed and which failed.\n",
755
- "hasJsonBody": false,
756
- "method": "POST",
757
- "operationId": "verifyDomain",
758
- "path": "/domains/{id}/verify",
759
- "pathParams": [
760
- {
761
- "description": "Resource UUID",
762
- "enum": null,
763
- "name": "id",
764
- "required": true,
765
- "type": "string"
766
- }
767
- ],
768
- "queryParams": [],
769
- "requestSchema": null,
770
- "responseSchema": {
771
- "oneOf": [
772
- {
773
- "type": "object",
774
- "properties": {
775
- "verified": {
776
- "type": "boolean",
777
- "const": true
778
- }
779
- },
780
- "required": [
781
- "verified"
782
- ]
783
- },
784
- {
785
- "type": "object",
786
- "properties": {
787
- "verified": {
788
- "type": "boolean",
789
- "const": false
790
- },
791
- "mxFound": {
792
- "type": "boolean",
793
- "description": "Whether MX records point to Primitive"
794
- },
795
- "txtFound": {
796
- "type": "boolean",
797
- "description": "Whether the TXT verification record was found"
798
- },
799
- "error": {
800
- "type": "string",
801
- "description": "Human-readable verification failure reason"
802
- }
803
- },
804
- "required": [
805
- "verified",
806
- "mxFound",
807
- "txtFound",
808
- "error"
809
- ]
810
- }
811
- ]
812
- },
813
- "sdkName": "verifyDomain",
814
- "summary": "Verify domain ownership",
815
- "tag": "Domains",
816
- "tagCommand": "domains"
817
- },
818
- {
819
- "binaryResponse": false,
820
- "bodyRequired": false,
821
- "command": "delete-email",
822
- "description": null,
823
- "hasJsonBody": false,
824
- "method": "DELETE",
825
- "operationId": "deleteEmail",
826
- "path": "/emails/{id}",
827
- "pathParams": [
828
- {
829
- "description": "Resource UUID",
830
- "enum": null,
831
- "name": "id",
832
- "required": true,
833
- "type": "string"
834
- }
835
- ],
836
- "queryParams": [],
837
- "requestSchema": null,
838
- "responseSchema": null,
839
- "sdkName": "deleteEmail",
840
- "summary": "Delete an email",
841
- "tag": "Emails",
842
- "tagCommand": "emails"
843
- },
844
- {
845
- "binaryResponse": false,
846
- "bodyRequired": false,
847
- "command": "discard-email-content",
848
- "description": "Permanently deletes the email's raw bytes, parsed body (text + HTML),\nand attachments while preserving metadata (sender, recipient,\nsubject, timestamps, hashes, attachment manifest) for audit logs.\nIdempotent: a second call returns success with\n`already_discarded: true` and does no work.\n\n**Gated** on the customer's discard-content opt-in (managed in the\ndashboard at Settings > Webhooks). When the toggle is off, this\nendpoint returns `403` with code `discard_not_enabled` and a\nmessage pointing the human at the dashboard. There is intentionally\nno API to flip this toggle. Opting in to a destructive,\nnon-reversible operation must be a deliberate human click in the\nUI.\n",
849
- "hasJsonBody": false,
850
- "method": "POST",
851
- "operationId": "discardEmailContent",
852
- "path": "/emails/{id}/discard-content",
853
- "pathParams": [
854
- {
855
- "description": "Resource UUID",
856
- "enum": null,
857
- "name": "id",
858
- "required": true,
859
- "type": "string"
860
- }
861
- ],
862
- "queryParams": [],
863
- "requestSchema": null,
864
- "responseSchema": {
865
- "type": "object",
866
- "properties": {
867
- "discarded": {
868
- "type": "boolean",
869
- "description": "Always `true` on a 2xx response. The content is either now\ndiscarded as a result of this call, or was already discarded\nbefore this call ran.\n"
870
- },
871
- "already_discarded": {
872
- "type": "boolean",
873
- "description": "`true` if the email's content was already discarded before\nthis call ran (no work was done). `false` if this call was\nthe one that performed the discard.\n"
874
- }
875
- },
876
- "required": [
877
- "discarded",
878
- "already_discarded"
879
- ]
880
- },
881
- "sdkName": "discardEmailContent",
882
- "summary": "Discard email content",
883
- "tag": "Emails",
884
- "tagCommand": "emails"
885
- },
886
- {
887
- "binaryResponse": true,
888
- "bodyRequired": false,
889
- "command": "download-attachments",
890
- "description": "Downloads all attachments as a gzip-compressed tar archive.\nAuthenticates via a signed download token (provided in webhook\npayloads) or a valid session.\n",
891
- "hasJsonBody": false,
892
- "method": "GET",
893
- "operationId": "downloadAttachments",
894
- "path": "/emails/{id}/attachments.tar.gz",
895
- "pathParams": [
896
- {
897
- "description": "Resource UUID",
898
- "enum": null,
899
- "name": "id",
900
- "required": true,
901
- "type": "string"
902
- }
903
- ],
904
- "queryParams": [
905
- {
906
- "description": "Signed download token from webhook payload",
907
- "enum": null,
908
- "name": "token",
909
- "required": false,
910
- "type": "string"
911
- }
912
- ],
913
- "requestSchema": null,
914
- "responseSchema": null,
915
- "sdkName": "downloadAttachments",
916
- "summary": "Download email attachments",
917
- "tag": "Emails",
918
- "tagCommand": "emails"
919
- },
920
- {
921
- "binaryResponse": true,
922
- "bodyRequired": false,
923
- "command": "download-raw-email",
924
- "description": "Downloads the raw RFC 822 email file (.eml). Authenticates via\na signed download token (provided in webhook payloads) or a\nvalid session.\n",
925
- "hasJsonBody": false,
926
- "method": "GET",
927
- "operationId": "downloadRawEmail",
928
- "path": "/emails/{id}/raw",
929
- "pathParams": [
930
- {
931
- "description": "Resource UUID",
932
- "enum": null,
933
- "name": "id",
934
- "required": true,
935
- "type": "string"
936
- }
937
- ],
938
- "queryParams": [
939
- {
940
- "description": "Signed download token from webhook payload",
941
- "enum": null,
942
- "name": "token",
943
- "required": false,
944
- "type": "string"
945
- }
946
- ],
947
- "requestSchema": null,
948
- "responseSchema": null,
949
- "sdkName": "downloadRawEmail",
950
- "summary": "Download raw email",
951
- "tag": "Emails",
952
- "tagCommand": "emails"
953
- },
954
- {
955
- "binaryResponse": false,
956
- "bodyRequired": false,
957
- "command": "get-email",
958
- "description": "Returns the full record for an inbound email received at one\nof your verified domains, including the parsed text and HTML\nbodies, threading metadata, SMTP envelope detail, webhook\ndelivery state, and a `replies` array for any outbound sends\nrecorded as replies to this inbound.\n\nFor listing inbound emails (with cursor pagination, status\nand date filters, and free-text search), use\n`/emails`. Outbound (sent) email records are NOT returned\nhere; use `/sent-emails/{id}` for those.\n\nThe response carries four sender-shaped fields whose\nmeanings overlap. `from_email` is the canonical \"who sent\nthis\" field for most use cases (parsed bare address from\nthe `From:` header, with a `sender` fallback). `from_header`\nis the raw header including any display name. `sender` and\n`smtp_mail_from` both carry the SMTP envelope MAIL FROM\n(return-path) and are equal by construction; `sender` is\nthe older field name retained for compatibility. See\n`primitive describe emails:get-email | jq '.responseSchema.properties'`\nfor per-field detail.\n",
959
- "hasJsonBody": false,
960
- "method": "GET",
961
- "operationId": "getEmail",
962
- "path": "/emails/{id}",
963
- "pathParams": [
964
- {
965
- "description": "Resource UUID",
966
- "enum": null,
967
- "name": "id",
968
- "required": true,
969
- "type": "string"
970
- }
971
- ],
972
- "queryParams": [],
973
- "requestSchema": null,
974
- "responseSchema": {
975
- "type": "object",
976
- "properties": {
977
- "id": {
978
- "type": "string",
979
- "format": "uuid"
980
- },
981
- "message_id": {
982
- "type": [
983
- "string",
984
- "null"
985
- ]
986
- },
987
- "domain_id": {
988
- "type": [
989
- "string",
990
- "null"
991
- ],
992
- "format": "uuid"
993
- },
994
- "org_id": {
995
- "type": [
996
- "string",
997
- "null"
998
- ],
999
- "format": "uuid"
1000
- },
1001
- "sender": {
1002
- "type": "string",
1003
- "description": "SMTP envelope sender (return-path) the inbound mail server\naccepted. Same value as `smtp_mail_from`; both fields exist\nso protocol-aware tooling can use whichever name it expects.\n\nFor most legitimate mail this equals `from_email`; for\nmailing lists, bounce handlers, and forwarders it is\ntypically the bounce-handling address rather than the\nhuman-visible sender.\n\n**For the canonical \"who sent this email\" value, use\n`from_email`.**\n"
1004
- },
1005
- "recipient": {
1006
- "type": "string"
1007
- },
1008
- "subject": {
1009
- "type": [
1010
- "string",
1011
- "null"
1012
- ]
1013
- },
1014
- "body_text": {
1015
- "type": [
1016
- "string",
1017
- "null"
1018
- ],
1019
- "description": "Plain-text body parsed from the inbound MIME, matching the `email.parsed.body_text` field on the webhook payload. Null when the message had no text part or parsing failed."
1020
- },
1021
- "body_html": {
1022
- "type": [
1023
- "string",
1024
- "null"
1025
- ],
1026
- "description": "HTML body parsed from the inbound MIME, matching the `email.parsed.body_html` field on the webhook payload. Null when the message had no HTML part or parsing failed."
1027
- },
1028
- "status": {
1029
- "type": "string",
1030
- "description": "Lifecycle status of an INBOUND email (a row in the `emails`\ntable). Distinct from `SentEmailStatus`, which describes\nthe OUTBOUND lifecycle (the `sent_emails` table) and uses\na different vocabulary because the lifecycles differ.\nPossible values:\n\n - `pending`: the row was inserted at ingestion (mx_main)\n and has not yet completed the spam / filter / auth\n pipeline. Body and parsed fields are present; webhook\n delivery is not yet scheduled. Most rows transition out\n of `pending` within seconds.\n - `accepted`: the inbound passed the policy gates and is\n queued for webhook delivery. The `webhook_status` field\n tracks the separate webhook-delivery lifecycle from\n this point.\n - `completed`: terminal success. Webhook delivery\n attempted and acknowledged by every active endpoint, OR\n no endpoints are configured, so the row is durably\n archived.\n - `rejected`: terminal failure at ingestion (spam, blocked\n sender, filter rule, malformed). The body and metadata\n are stored for auditing but no webhook fires and the\n row is not repliable.\n\nSee also `webhook_status` (separate enum tracking the\nwebhook-delivery state machine) and `SentEmailStatus` (the\noutbound vocabulary).\n",
1031
- "enum": [
1032
- "pending",
1033
- "accepted",
1034
- "completed",
1035
- "rejected"
1036
- ]
1037
- },
1038
- "domain": {
1039
- "type": "string"
1040
- },
1041
- "spam_score": {
1042
- "type": [
1043
- "number",
1044
- "null"
1045
- ]
1046
- },
1047
- "raw_size_bytes": {
1048
- "type": [
1049
- "integer",
1050
- "null"
1051
- ]
1052
- },
1053
- "raw_sha256": {
1054
- "type": [
1055
- "string",
1056
- "null"
1057
- ]
1058
- },
1059
- "created_at": {
1060
- "type": "string",
1061
- "format": "date-time"
1062
- },
1063
- "received_at": {
1064
- "type": "string",
1065
- "format": "date-time"
1066
- },
1067
- "rejection_reason": {
1068
- "type": [
1069
- "string",
1070
- "null"
1071
- ]
1072
- },
1073
- "webhook_status": {
1074
- "type": [
1075
- "string",
1076
- "null"
1077
- ],
1078
- "description": "Webhook-delivery state for an inbound email. Tracks a\nSEPARATE lifecycle from the email's `status` field; the\nsame row carries both. Possible values:\n\n - `pending`: ingestion is past `pending` (the email itself\n is `accepted`) but the webhook fan-out has not yet\n started for this row.\n - `in_flight`: at least one delivery attempt is in flight.\n - `fired`: terminal success. Every active endpoint\n acknowledged the delivery (or accepted it after retries).\n - `failed`: terminal partial-failure. At least one endpoint\n exhausted its retry budget; some endpoints may still\n have succeeded.\n - `exhausted`: terminal failure. Every endpoint exhausted\n its retry budget without success.\n - `null`: no endpoints configured, so no webhook lifecycle\n applies.\n\nNote that the value `pending` here does NOT mean the email\nis `pending`; it means the email is past ingestion but\nwebhook delivery has not yet begun. Two overlapping uses\nof the word `pending` for distinct lifecycle phases.\n",
1079
- "enum": [
1080
- "pending",
1081
- "in_flight",
1082
- "fired",
1083
- "failed",
1084
- "exhausted",
1085
- null
1086
- ]
1087
- },
1088
- "webhook_attempt_count": {
1089
- "type": "integer"
1090
- },
1091
- "webhook_last_attempt_at": {
1092
- "type": [
1093
- "string",
1094
- "null"
1095
- ],
1096
- "format": "date-time"
1097
- },
1098
- "webhook_last_status_code": {
1099
- "type": [
1100
- "integer",
1101
- "null"
1102
- ]
1103
- },
1104
- "webhook_last_error": {
1105
- "type": [
1106
- "string",
1107
- "null"
1108
- ]
1109
- },
1110
- "webhook_fired_at": {
1111
- "type": [
1112
- "string",
1113
- "null"
1114
- ],
1115
- "format": "date-time"
1116
- },
1117
- "smtp_helo": {
1118
- "type": [
1119
- "string",
1120
- "null"
1121
- ]
1122
- },
1123
- "smtp_mail_from": {
1124
- "type": [
1125
- "string",
1126
- "null"
1127
- ],
1128
- "description": "SMTP envelope MAIL FROM (return-path), as accepted by the\ninbound mail server. Same value as `sender`; both fields\nexist so protocol-aware tooling can use whichever name it\nexpects.\n\nFor the canonical \"who sent this email\" value (display name\nstripped, From-header preferred), use `from_email`.\n"
1129
- },
1130
- "smtp_rcpt_to": {
1131
- "type": [
1132
- "array",
1133
- "null"
1134
- ],
1135
- "items": {
1136
- "type": "string"
1137
- }
1138
- },
1139
- "from_header": {
1140
- "type": [
1141
- "string",
1142
- "null"
1143
- ],
1144
- "description": "Raw `From:` header from the message body, including any\ndisplay name (e.g. `\"Alice Example\" <alice@example.com>`).\nUse this when you need the display name for rendering.\n\nFor the bare email address (display name stripped), use\n`from_email`.\n"
1145
- },
1146
- "content_discarded_at": {
1147
- "type": [
1148
- "string",
1149
- "null"
1150
- ],
1151
- "format": "date-time"
1152
- },
1153
- "content_discarded_by_delivery_id": {
1154
- "type": [
1155
- "string",
1156
- "null"
1157
- ]
1158
- },
1159
- "from_email": {
1160
- "type": "string",
1161
- "description": "Bare email address parsed from the `From:` header, with\ndisplay name stripped (e.g. `alice@example.com`). Falls\nback to `sender` (the SMTP envelope MAIL FROM) when the\n`From:` header cannot be parsed.\n\n**This is the canonical \"who sent this email\" field for\nmost use cases**, including comparing against allowlists,\nrouting replies, or displaying the sender to a user. Use\n`from_header` when you specifically need the display name,\nor `sender`/`smtp_mail_from` when you need the SMTP\nenvelope value (e.g. to follow a bounce).\n"
1162
- },
1163
- "to_email": {
1164
- "type": "string",
1165
- "description": "Parsed to address (same as recipient)"
1166
- },
1167
- "from_known_address": {
1168
- "type": "boolean",
1169
- "description": "True when the inbound's sender address has a matching grant\nin the org's known-send-addresses list. Advisory: a true\nvalue does not by itself guarantee that a reply will be\naccepted by send-mail's gates; the per-send check at send\ntime remains authoritative.\n"
1170
- },
1171
- "replies": {
1172
- "type": "array",
1173
- "description": "Sent emails recorded as replies to this inbound, in send\norder (ascending). Populated when a customer's send-mail\nrequest carries an `in_reply_to` Message-ID that matches\nthis inbound's `message_id` in the same org. Includes\nattempts that were gate-denied, so the array reflects every\nrecorded reply attempt regardless of outcome.\n",
1174
- "items": {
1175
- "type": "object",
1176
- "properties": {
1177
- "id": {
1178
- "type": "string",
1179
- "format": "uuid",
1180
- "description": "Sent-email row id."
1181
- },
1182
- "status": {
1183
- "type": "string",
1184
- "description": "Lifecycle status of a sent_emails row. Possible values:\n\n - `queued`: pre-call INSERT; the outbound agent has not\n yet replied.\n - `submitted_to_agent`: agent accepted; `queue_id` is set.\n - `agent_failed`: agent rejected; `error_code` and\n `error_message` carry the reason.\n - `gate_denied`: a recipient-scope gate denied the send;\n the agent was never called. The `gates` array carries\n the denial detail. /send-mail returns 403 in this case\n so callers see the denial synchronously; /sent-emails\n additionally records the row for historical lookup,\n which is when this status appears in a listing.\n - `unknown`: terminal indeterminate; the on-box log\n poller couldn't classify the receiver's response.\n - `delivered` / `bounced` / `deferred` / `wait_timeout`:\n terminal delivery outcomes (see DeliveryStatus).\n",
1185
- "enum": [
1186
- "queued",
1187
- "submitted_to_agent",
1188
- "agent_failed",
1189
- "gate_denied",
1190
- "unknown",
1191
- "delivered",
1192
- "bounced",
1193
- "deferred",
1194
- "wait_timeout"
1195
- ]
1196
- },
1197
- "to_address": {
1198
- "type": "string",
1199
- "description": "Recipient address as recorded on the sent_emails row."
1200
- },
1201
- "subject": {
1202
- "type": [
1203
- "string",
1204
- "null"
1205
- ]
1206
- },
1207
- "created_at": {
1208
- "type": "string",
1209
- "format": "date-time"
1210
- },
1211
- "queue_id": {
1212
- "type": [
1213
- "string",
1214
- "null"
1215
- ],
1216
- "description": "Outbound relay queue identifier when available."
1217
- }
1218
- },
1219
- "required": [
1220
- "id",
1221
- "status",
1222
- "to_address",
1223
- "created_at"
1224
- ]
1225
- }
1226
- }
1227
- },
1228
- "required": [
1229
- "id",
1230
- "sender",
1231
- "recipient",
1232
- "status",
1233
- "domain",
1234
- "created_at",
1235
- "received_at",
1236
- "webhook_attempt_count",
1237
- "from_email",
1238
- "to_email",
1239
- "replies"
1240
- ]
1241
- },
1242
- "sdkName": "getEmail",
1243
- "summary": "Get inbound email by id",
1244
- "tag": "Emails",
1245
- "tagCommand": "emails"
1246
- },
1247
- {
1248
- "binaryResponse": false,
1249
- "bodyRequired": false,
1250
- "command": "list-emails",
1251
- "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",
1252
- "hasJsonBody": false,
1253
- "method": "GET",
1254
- "operationId": "listEmails",
1255
- "path": "/emails",
1256
- "pathParams": [],
1257
- "queryParams": [
1258
- {
1259
- "description": "Pagination cursor from a previous response's `meta.cursor` field.\nFormat: `{ISO-datetime}|{id}`\n",
1260
- "enum": null,
1261
- "name": "cursor",
1262
- "required": false,
1263
- "type": "string"
1264
- },
1265
- {
1266
- "description": "Number of results per page",
1267
- "enum": null,
1268
- "name": "limit",
1269
- "required": false,
1270
- "type": "integer"
1271
- },
1272
- {
1273
- "description": "Filter by domain ID",
1274
- "enum": null,
1275
- "name": "domain_id",
1276
- "required": false,
1277
- "type": "string"
1278
- },
1279
- {
1280
- "description": "Filter inbound rows by lifecycle status. See `EmailStatus`\nfor what each value means. Note that the webhook delivery\nstate is a SEPARATE lifecycle on the same row; filter by\n`webhook_status` semantics is not currently supported on\nthis endpoint.\n",
1281
- "enum": null,
1282
- "name": "status",
1283
- "required": false,
1284
- "type": "string"
1285
- },
1286
- {
1287
- "description": "Search subject, sender, and recipient (case-insensitive)",
1288
- "enum": null,
1289
- "name": "search",
1290
- "required": false,
1291
- "type": "string"
1292
- },
1293
- {
1294
- "description": "Filter emails created on or after this timestamp",
1295
- "enum": null,
1296
- "name": "date_from",
1297
- "required": false,
1298
- "type": "string"
1299
- },
1300
- {
1301
- "description": "Filter emails created on or before this timestamp",
1302
- "enum": null,
1303
- "name": "date_to",
1304
- "required": false,
1305
- "type": "string"
1306
- }
1307
- ],
1308
- "requestSchema": null,
1309
- "responseSchema": {
1310
- "type": "array",
1311
- "items": {
1312
- "type": "object",
1313
- "properties": {
1314
- "id": {
1315
- "type": "string",
1316
- "format": "uuid"
1317
- },
1318
- "message_id": {
1319
- "type": [
1320
- "string",
1321
- "null"
1322
- ]
1323
- },
1324
- "domain_id": {
1325
- "type": [
1326
- "string",
1327
- "null"
1328
- ],
1329
- "format": "uuid"
1330
- },
1331
- "org_id": {
1332
- "type": [
1333
- "string",
1334
- "null"
1335
- ],
1336
- "format": "uuid"
1337
- },
1338
- "status": {
1339
- "type": "string",
1340
- "description": "Lifecycle status of an INBOUND email (a row in the `emails`\ntable). Distinct from `SentEmailStatus`, which describes\nthe OUTBOUND lifecycle (the `sent_emails` table) and uses\na different vocabulary because the lifecycles differ.\nPossible values:\n\n - `pending`: the row was inserted at ingestion (mx_main)\n and has not yet completed the spam / filter / auth\n pipeline. Body and parsed fields are present; webhook\n delivery is not yet scheduled. Most rows transition out\n of `pending` within seconds.\n - `accepted`: the inbound passed the policy gates and is\n queued for webhook delivery. The `webhook_status` field\n tracks the separate webhook-delivery lifecycle from\n this point.\n - `completed`: terminal success. Webhook delivery\n attempted and acknowledged by every active endpoint, OR\n no endpoints are configured, so the row is durably\n archived.\n - `rejected`: terminal failure at ingestion (spam, blocked\n sender, filter rule, malformed). The body and metadata\n are stored for auditing but no webhook fires and the\n row is not repliable.\n\nSee also `webhook_status` (separate enum tracking the\nwebhook-delivery state machine) and `SentEmailStatus` (the\noutbound vocabulary).\n",
1341
- "enum": [
1342
- "pending",
1343
- "accepted",
1344
- "completed",
1345
- "rejected"
1346
- ]
1347
- },
1348
- "sender": {
1349
- "type": "string",
1350
- "description": "SMTP envelope sender (return-path) the inbound mail server\naccepted. For most legitimate mail this equals the bare\naddress in the From header; for mailing lists, bounce\nhandlers, and forwarders it is typically the bounce address\nrather than the human-visible sender.\n\nFor the parsed From-header value (with display name handling\nand a sender-fallback when the header is unparseable), GET\nthe email by id and use `from_email`.\n"
1351
- },
1352
- "recipient": {
1353
- "type": "string"
1354
- },
1355
- "subject": {
1356
- "type": [
1357
- "string",
1358
- "null"
1359
- ]
1360
- },
1361
- "domain": {
1362
- "type": "string"
1363
- },
1364
- "spam_score": {
1365
- "type": [
1366
- "number",
1367
- "null"
1368
- ]
1369
- },
1370
- "created_at": {
1371
- "type": "string",
1372
- "format": "date-time"
1373
- },
1374
- "received_at": {
1375
- "type": "string",
1376
- "format": "date-time"
1377
- },
1378
- "raw_size_bytes": {
1379
- "type": [
1380
- "integer",
1381
- "null"
1382
- ]
1383
- },
1384
- "webhook_status": {
1385
- "type": [
1386
- "string",
1387
- "null"
1388
- ],
1389
- "description": "Webhook-delivery state for an inbound email. Tracks a\nSEPARATE lifecycle from the email's `status` field; the\nsame row carries both. Possible values:\n\n - `pending`: ingestion is past `pending` (the email itself\n is `accepted`) but the webhook fan-out has not yet\n started for this row.\n - `in_flight`: at least one delivery attempt is in flight.\n - `fired`: terminal success. Every active endpoint\n acknowledged the delivery (or accepted it after retries).\n - `failed`: terminal partial-failure. At least one endpoint\n exhausted its retry budget; some endpoints may still\n have succeeded.\n - `exhausted`: terminal failure. Every endpoint exhausted\n its retry budget without success.\n - `null`: no endpoints configured, so no webhook lifecycle\n applies.\n\nNote that the value `pending` here does NOT mean the email\nis `pending`; it means the email is past ingestion but\nwebhook delivery has not yet begun. Two overlapping uses\nof the word `pending` for distinct lifecycle phases.\n",
1390
- "enum": [
1391
- "pending",
1392
- "in_flight",
1393
- "fired",
1394
- "failed",
1395
- "exhausted",
1396
- null
1397
- ]
1398
- },
1399
- "webhook_attempt_count": {
1400
- "type": "integer"
1401
- }
1402
- },
1403
- "required": [
1404
- "id",
1405
- "status",
1406
- "sender",
1407
- "recipient",
1408
- "domain",
1409
- "created_at",
1410
- "received_at",
1411
- "webhook_attempt_count"
1412
- ]
1413
- }
1414
- },
1415
- "sdkName": "listEmails",
1416
- "summary": "List inbound emails",
1417
- "tag": "Emails",
1418
- "tagCommand": "emails"
1419
- },
1420
- {
1421
- "binaryResponse": false,
1422
- "bodyRequired": false,
1423
- "command": "replay-email-webhooks",
1424
- "description": "Re-delivers the webhook payload for this email to all active\nendpoints matching the email's domain. Rate limited per-email\n(short cooldown between successive replays of the same email)\nand per-org (burst + sustained windows), sharing an org-wide\nbudget with delivery replays.\n",
1425
- "hasJsonBody": false,
1426
- "method": "POST",
1427
- "operationId": "replayEmailWebhooks",
1428
- "path": "/emails/{id}/replay",
1429
- "pathParams": [
1430
- {
1431
- "description": "Resource UUID",
1432
- "enum": null,
1433
- "name": "id",
1434
- "required": true,
1435
- "type": "string"
1436
- }
1437
- ],
1438
- "queryParams": [],
1439
- "requestSchema": null,
1440
- "responseSchema": {
1441
- "type": "object",
1442
- "properties": {
1443
- "delivered": {
1444
- "type": "integer",
1445
- "description": "Number of successful deliveries"
1446
- },
1447
- "failed": {
1448
- "type": "integer",
1449
- "description": "Number of failed deliveries"
1450
- }
1451
- },
1452
- "required": [
1453
- "delivered",
1454
- "failed"
1455
- ]
1456
- },
1457
- "sdkName": "replayEmailWebhooks",
1458
- "summary": "Replay email webhooks",
1459
- "tag": "Emails",
1460
- "tagCommand": "emails"
1461
- },
1462
- {
1463
- "binaryResponse": false,
1464
- "bodyRequired": false,
1465
- "command": "search-emails",
1466
- "description": "Searches inbound emails with structured filters and optional\nfull-text matching across parsed email fields. This endpoint is\noptimized for filtered inbox views and CLI polling workflows:\ncallers that only need new accepted mail can pass\n`sort=received_at_asc`, `snippet=false`, `include_facets=false`,\nand a `date_from` timestamp.\n\n`q`, `subject`, and `body` use the same English full-text index\nas the web inbox search. Structured filters such as `from`, `to`,\n`domain_id`, status, attachment presence, and spam score bounds\nare combined with the text query.\n",
1467
- "hasJsonBody": false,
1468
- "method": "GET",
1469
- "operationId": "searchEmails",
1470
- "path": "/emails/search",
1471
- "pathParams": [],
1472
- "queryParams": [
1473
- {
1474
- "description": "Full-text search DSL query.",
1475
- "enum": null,
1476
- "name": "q",
1477
- "required": false,
1478
- "type": "string"
1479
- },
1480
- {
1481
- "description": "Filter by sender address or sender domain.",
1482
- "enum": null,
1483
- "name": "from",
1484
- "required": false,
1485
- "type": "string"
1486
- },
1487
- {
1488
- "description": "Filter by recipient address or recipient domain.",
1489
- "enum": null,
1490
- "name": "to",
1491
- "required": false,
1492
- "type": "string"
1493
- },
1494
- {
1495
- "description": "Full-text search restricted to the subject field.",
1496
- "enum": null,
1497
- "name": "subject",
1498
- "required": false,
1499
- "type": "string"
1500
- },
1501
- {
1502
- "description": "Full-text search restricted to the parsed text body.",
1503
- "enum": null,
1504
- "name": "body",
1505
- "required": false,
1506
- "type": "string"
1507
- },
1508
- {
1509
- "description": "Filter by domain ID.",
1510
- "enum": null,
1511
- "name": "domain_id",
1512
- "required": false,
1513
- "type": "string"
1514
- },
1515
- {
1516
- "description": "Filter by inbound email lifecycle status.",
1517
- "enum": null,
1518
- "name": "status",
1519
- "required": false,
1520
- "type": "string"
1521
- },
1522
- {
1523
- "description": "Filter emails received on or after this timestamp.",
1524
- "enum": null,
1525
- "name": "date_from",
1526
- "required": false,
1527
- "type": "string"
1528
- },
1529
- {
1530
- "description": "Filter emails received on or before this timestamp.",
1531
- "enum": null,
1532
- "name": "date_to",
1533
- "required": false,
1534
- "type": "string"
1535
- },
1536
- {
1537
- "description": "Filter by whether the email has one or more attachments.",
1538
- "enum": [
1539
- "true",
1540
- "false"
1541
- ],
1542
- "name": "has_attachment",
1543
- "required": false,
1544
- "type": "string"
1545
- },
1546
- {
1547
- "description": "Filter to emails with spam score below this value.",
1548
- "enum": null,
1549
- "name": "spam_score_lt",
1550
- "required": false,
1551
- "type": "number"
1552
- },
1553
- {
1554
- "description": "Filter to emails with spam score greater than or equal to this value.",
1555
- "enum": null,
1556
- "name": "spam_score_gte",
1557
- "required": false,
1558
- "type": "number"
1559
- },
1560
- {
1561
- "description": "Sort mode. Defaults to relevance when a text query is present,\notherwise `received_at_desc`.\n",
1562
- "enum": [
1563
- "relevance",
1564
- "received_at_desc",
1565
- "received_at_asc"
1566
- ],
1567
- "name": "sort",
1568
- "required": false,
1569
- "type": "string"
1570
- },
1571
- {
1572
- "description": "Opaque pagination cursor from a previous search response.",
1573
- "enum": null,
1574
- "name": "cursor",
1575
- "required": false,
1576
- "type": "string"
1577
- },
1578
- {
1579
- "description": "Number of results per page",
1580
- "enum": null,
1581
- "name": "limit",
1582
- "required": false,
1583
- "type": "integer"
1584
- },
1585
- {
1586
- "description": "Include subject/body highlight snippets when text search is active.",
1587
- "enum": [
1588
- "true",
1589
- "false"
1590
- ],
1591
- "name": "snippet",
1592
- "required": false,
1593
- "type": "string"
1594
- },
1595
- {
1596
- "description": "Include facet counts for sender, domain, status, and attachment presence.",
1597
- "enum": [
1598
- "true",
1599
- "false"
1600
- ],
1601
- "name": "include_facets",
1602
- "required": false,
1603
- "type": "string"
1604
- }
1605
- ],
1606
- "requestSchema": null,
1607
- "responseSchema": {
1608
- "type": "array",
1609
- "items": {
1610
- "allOf": [
1611
- {
1612
- "type": "object",
1613
- "properties": {
1614
- "id": {
1615
- "type": "string",
1616
- "format": "uuid"
1617
- },
1618
- "message_id": {
1619
- "type": [
1620
- "string",
1621
- "null"
1622
- ]
1623
- },
1624
- "domain_id": {
1625
- "type": [
1626
- "string",
1627
- "null"
1628
- ],
1629
- "format": "uuid"
1630
- },
1631
- "org_id": {
1632
- "type": [
1633
- "string",
1634
- "null"
1635
- ],
1636
- "format": "uuid"
1637
- },
1638
- "status": {
1639
- "type": "string",
1640
- "description": "Lifecycle status of an INBOUND email (a row in the `emails`\ntable). Distinct from `SentEmailStatus`, which describes\nthe OUTBOUND lifecycle (the `sent_emails` table) and uses\na different vocabulary because the lifecycles differ.\nPossible values:\n\n - `pending`: the row was inserted at ingestion (mx_main)\n and has not yet completed the spam / filter / auth\n pipeline. Body and parsed fields are present; webhook\n delivery is not yet scheduled. Most rows transition out\n of `pending` within seconds.\n - `accepted`: the inbound passed the policy gates and is\n queued for webhook delivery. The `webhook_status` field\n tracks the separate webhook-delivery lifecycle from\n this point.\n - `completed`: terminal success. Webhook delivery\n attempted and acknowledged by every active endpoint, OR\n no endpoints are configured, so the row is durably\n archived.\n - `rejected`: terminal failure at ingestion (spam, blocked\n sender, filter rule, malformed). The body and metadata\n are stored for auditing but no webhook fires and the\n row is not repliable.\n\nSee also `webhook_status` (separate enum tracking the\nwebhook-delivery state machine) and `SentEmailStatus` (the\noutbound vocabulary).\n",
1641
- "enum": [
1642
- "pending",
1643
- "accepted",
1644
- "completed",
1645
- "rejected"
1646
- ]
1647
- },
1648
- "sender": {
1649
- "type": "string",
1650
- "description": "SMTP envelope sender (return-path) the inbound mail server\naccepted. For most legitimate mail this equals the bare\naddress in the From header; for mailing lists, bounce\nhandlers, and forwarders it is typically the bounce address\nrather than the human-visible sender.\n\nFor the parsed From-header value (with display name handling\nand a sender-fallback when the header is unparseable), GET\nthe email by id and use `from_email`.\n"
1651
- },
1652
- "recipient": {
1653
- "type": "string"
1654
- },
1655
- "subject": {
1656
- "type": [
1657
- "string",
1658
- "null"
1659
- ]
1660
- },
1661
- "domain": {
1662
- "type": "string"
1663
- },
1664
- "spam_score": {
1665
- "type": [
1666
- "number",
1667
- "null"
1668
- ]
1669
- },
1670
- "created_at": {
1671
- "type": "string",
1672
- "format": "date-time"
1673
- },
1674
- "received_at": {
1675
- "type": "string",
1676
- "format": "date-time"
1677
- },
1678
- "raw_size_bytes": {
1679
- "type": [
1680
- "integer",
1681
- "null"
1682
- ]
1683
- },
1684
- "webhook_status": {
1685
- "type": [
1686
- "string",
1687
- "null"
1688
- ],
1689
- "description": "Webhook-delivery state for an inbound email. Tracks a\nSEPARATE lifecycle from the email's `status` field; the\nsame row carries both. Possible values:\n\n - `pending`: ingestion is past `pending` (the email itself\n is `accepted`) but the webhook fan-out has not yet\n started for this row.\n - `in_flight`: at least one delivery attempt is in flight.\n - `fired`: terminal success. Every active endpoint\n acknowledged the delivery (or accepted it after retries).\n - `failed`: terminal partial-failure. At least one endpoint\n exhausted its retry budget; some endpoints may still\n have succeeded.\n - `exhausted`: terminal failure. Every endpoint exhausted\n its retry budget without success.\n - `null`: no endpoints configured, so no webhook lifecycle\n applies.\n\nNote that the value `pending` here does NOT mean the email\nis `pending`; it means the email is past ingestion but\nwebhook delivery has not yet begun. Two overlapping uses\nof the word `pending` for distinct lifecycle phases.\n",
1690
- "enum": [
1691
- "pending",
1692
- "in_flight",
1693
- "fired",
1694
- "failed",
1695
- "exhausted",
1696
- null
1697
- ]
1698
- },
1699
- "webhook_attempt_count": {
1700
- "type": "integer"
1701
- }
1702
- },
1703
- "required": [
1704
- "id",
1705
- "status",
1706
- "sender",
1707
- "recipient",
1708
- "domain",
1709
- "created_at",
1710
- "received_at",
1711
- "webhook_attempt_count"
1712
- ]
1713
- },
1714
- {
1715
- "type": "object",
1716
- "properties": {
1717
- "attachment_count": {
1718
- "type": "integer",
1719
- "description": "Number of parsed attachments on the email."
1720
- },
1721
- "from_known_address": {
1722
- "type": "boolean",
1723
- "description": "Whether the parsed From address is known to this org from prior authenticated inbound mail."
1724
- },
1725
- "score": {
1726
- "type": "number",
1727
- "description": "Relevance score. Present only when sorting by relevance."
1728
- },
1729
- "highlights": {
1730
- "type": "object",
1731
- "properties": {
1732
- "subject": {
1733
- "type": "array",
1734
- "items": {
1735
- "type": "string"
1736
- },
1737
- "description": "Subject snippets with matching terms highlighted."
1738
- },
1739
- "body": {
1740
- "type": "array",
1741
- "items": {
1742
- "type": "string"
1743
- },
1744
- "description": "Body snippets with matching terms highlighted."
1745
- }
1746
- },
1747
- "required": [
1748
- "subject",
1749
- "body"
1750
- ]
1751
- }
1752
- },
1753
- "required": [
1754
- "attachment_count",
1755
- "from_known_address"
1756
- ]
1757
- }
1758
- ]
1759
- }
1760
- },
1761
- "sdkName": "searchEmails",
1762
- "summary": "Search inbound emails",
1763
- "tag": "Emails",
1764
- "tagCommand": "emails"
1765
- },
1766
- {
1767
- "binaryResponse": false,
1768
- "bodyRequired": true,
1769
- "command": "create-endpoint",
1770
- "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",
1771
- "hasJsonBody": true,
1772
- "method": "POST",
1773
- "operationId": "createEndpoint",
1774
- "path": "/endpoints",
1775
- "pathParams": [],
1776
- "queryParams": [],
1777
- "requestSchema": {
1778
- "type": "object",
1779
- "additionalProperties": false,
1780
- "properties": {
1781
- "url": {
1782
- "type": "string",
1783
- "minLength": 1,
1784
- "description": "The webhook URL to deliver events to"
1785
- },
1786
- "enabled": {
1787
- "type": "boolean",
1788
- "default": true,
1789
- "description": "Whether the endpoint is active"
1790
- },
1791
- "domain_id": {
1792
- "type": [
1793
- "string",
1794
- "null"
1795
- ],
1796
- "format": "uuid",
1797
- "description": "Restrict to emails from a specific domain"
1798
- },
1799
- "rules": {
1800
- "type": "object",
1801
- "description": "Endpoint-specific filtering rules"
1802
- }
1803
- },
1804
- "required": [
1805
- "url"
1806
- ]
1807
- },
1808
- "responseSchema": {
1809
- "type": "object",
1810
- "properties": {
1811
- "id": {
1812
- "type": "string",
1813
- "format": "uuid"
1814
- },
1815
- "org_id": {
1816
- "type": "string",
1817
- "format": "uuid"
1818
- },
1819
- "url": {
1820
- "type": [
1821
- "string",
1822
- "null"
1823
- ]
1824
- },
1825
- "enabled": {
1826
- "type": "boolean"
1827
- },
1828
- "domain_id": {
1829
- "type": [
1830
- "string",
1831
- "null"
1832
- ],
1833
- "format": "uuid",
1834
- "description": "Restrict this endpoint to emails from a specific domain"
1835
- },
1836
- "rules": {
1837
- "type": "object",
1838
- "description": "Endpoint-specific filtering rules"
1839
- },
1840
- "created_at": {
1841
- "type": "string",
1842
- "format": "date-time"
1843
- },
1844
- "updated_at": {
1845
- "type": "string",
1846
- "format": "date-time"
1847
- },
1848
- "delivery_count": {
1849
- "type": "integer",
1850
- "description": "Total webhook deliveries attempted"
1851
- },
1852
- "success_count": {
1853
- "type": "integer",
1854
- "description": "Successful deliveries"
1855
- },
1856
- "failure_count": {
1857
- "type": "integer",
1858
- "description": "Failed deliveries"
1859
- },
1860
- "consecutive_fails": {
1861
- "type": "integer",
1862
- "description": "Current streak of consecutive failures"
1863
- },
1864
- "last_delivery_at": {
1865
- "type": [
1866
- "string",
1867
- "null"
1868
- ],
1869
- "format": "date-time"
1870
- },
1871
- "last_success_at": {
1872
- "type": [
1873
- "string",
1874
- "null"
1875
- ],
1876
- "format": "date-time"
1877
- },
1878
- "last_failure_at": {
1879
- "type": [
1880
- "string",
1881
- "null"
1882
- ],
1883
- "format": "date-time"
1884
- },
1885
- "deactivated_at": {
1886
- "type": [
1887
- "string",
1888
- "null"
1889
- ],
1890
- "format": "date-time"
1891
- }
1892
- },
1893
- "required": [
1894
- "id",
1895
- "org_id",
1896
- "enabled",
1897
- "rules",
1898
- "created_at",
1899
- "updated_at",
1900
- "delivery_count",
1901
- "success_count",
1902
- "failure_count",
1903
- "consecutive_fails"
1904
- ]
1905
- },
1906
- "sdkName": "createEndpoint",
1907
- "summary": "Create a webhook endpoint",
1908
- "tag": "Endpoints",
1909
- "tagCommand": "endpoints"
1910
- },
1911
- {
1912
- "binaryResponse": false,
1913
- "bodyRequired": false,
1914
- "command": "delete-endpoint",
1915
- "description": "Soft-deletes a webhook endpoint. The endpoint will no longer\nreceive webhook deliveries.\n",
1916
- "hasJsonBody": false,
1917
- "method": "DELETE",
1918
- "operationId": "deleteEndpoint",
1919
- "path": "/endpoints/{id}",
1920
- "pathParams": [
1921
- {
1922
- "description": "Resource UUID",
1923
- "enum": null,
1924
- "name": "id",
1925
- "required": true,
1926
- "type": "string"
1927
- }
1928
- ],
1929
- "queryParams": [],
1930
- "requestSchema": null,
1931
- "responseSchema": null,
1932
- "sdkName": "deleteEndpoint",
1933
- "summary": "Delete a webhook endpoint",
1934
- "tag": "Endpoints",
1935
- "tagCommand": "endpoints"
1936
- },
1937
- {
1938
- "binaryResponse": false,
1939
- "bodyRequired": false,
1940
- "command": "list-endpoints",
1941
- "description": "Returns all active (non-deleted) webhook endpoints.",
1942
- "hasJsonBody": false,
1943
- "method": "GET",
1944
- "operationId": "listEndpoints",
1945
- "path": "/endpoints",
1946
- "pathParams": [],
1947
- "queryParams": [],
1948
- "requestSchema": null,
1949
- "responseSchema": {
1950
- "type": "array",
1951
- "items": {
1952
- "type": "object",
1953
- "properties": {
1954
- "id": {
1955
- "type": "string",
1956
- "format": "uuid"
1957
- },
1958
- "org_id": {
1959
- "type": "string",
1960
- "format": "uuid"
1961
- },
1962
- "url": {
1963
- "type": [
1964
- "string",
1965
- "null"
1966
- ]
1967
- },
1968
- "enabled": {
1969
- "type": "boolean"
1970
- },
1971
- "domain_id": {
1972
- "type": [
1973
- "string",
1974
- "null"
1975
- ],
1976
- "format": "uuid",
1977
- "description": "Restrict this endpoint to emails from a specific domain"
1978
- },
1979
- "rules": {
1980
- "type": "object",
1981
- "description": "Endpoint-specific filtering rules"
1982
- },
1983
- "created_at": {
1984
- "type": "string",
1985
- "format": "date-time"
1986
- },
1987
- "updated_at": {
1988
- "type": "string",
1989
- "format": "date-time"
1990
- },
1991
- "delivery_count": {
1992
- "type": "integer",
1993
- "description": "Total webhook deliveries attempted"
1994
- },
1995
- "success_count": {
1996
- "type": "integer",
1997
- "description": "Successful deliveries"
1998
- },
1999
- "failure_count": {
2000
- "type": "integer",
2001
- "description": "Failed deliveries"
2002
- },
2003
- "consecutive_fails": {
2004
- "type": "integer",
2005
- "description": "Current streak of consecutive failures"
2006
- },
2007
- "last_delivery_at": {
2008
- "type": [
2009
- "string",
2010
- "null"
2011
- ],
2012
- "format": "date-time"
2013
- },
2014
- "last_success_at": {
2015
- "type": [
2016
- "string",
2017
- "null"
2018
- ],
2019
- "format": "date-time"
2020
- },
2021
- "last_failure_at": {
2022
- "type": [
2023
- "string",
2024
- "null"
2025
- ],
2026
- "format": "date-time"
2027
- },
2028
- "deactivated_at": {
2029
- "type": [
2030
- "string",
2031
- "null"
2032
- ],
2033
- "format": "date-time"
2034
- }
2035
- },
2036
- "required": [
2037
- "id",
2038
- "org_id",
2039
- "enabled",
2040
- "rules",
2041
- "created_at",
2042
- "updated_at",
2043
- "delivery_count",
2044
- "success_count",
2045
- "failure_count",
2046
- "consecutive_fails"
2047
- ]
2048
- }
2049
- },
2050
- "sdkName": "listEndpoints",
2051
- "summary": "List webhook endpoints",
2052
- "tag": "Endpoints",
2053
- "tagCommand": "endpoints"
2054
- },
2055
- {
2056
- "binaryResponse": false,
2057
- "bodyRequired": false,
2058
- "command": "test-endpoint",
2059
- "description": "Sends a sample `email.received` event to the endpoint. The request\nincludes SSRF protection (private IP rejection and DNS pinning).\nRate limited to 4 per minute and 30 per hour (non-exempt).\nSuccessful deliveries and verified-domain endpoints are exempt\nfrom the rate limit.\n",
2060
- "hasJsonBody": false,
2061
- "method": "POST",
2062
- "operationId": "testEndpoint",
2063
- "path": "/endpoints/{id}/test",
2064
- "pathParams": [
2065
- {
2066
- "description": "Resource UUID",
2067
- "enum": null,
2068
- "name": "id",
2069
- "required": true,
2070
- "type": "string"
2071
- }
2072
- ],
2073
- "queryParams": [],
2074
- "requestSchema": null,
2075
- "responseSchema": {
2076
- "type": "object",
2077
- "properties": {
2078
- "status": {
2079
- "type": "integer",
2080
- "description": "HTTP status code returned by the endpoint"
2081
- },
2082
- "body": {
2083
- "type": "string",
2084
- "description": "Response body (truncated to 1000 characters)"
2085
- },
2086
- "signature": {
2087
- "type": "string",
2088
- "description": "The signature header value sent (if webhook secret is configured)"
2089
- }
2090
- },
2091
- "required": [
2092
- "status",
2093
- "body"
2094
- ]
2095
- },
2096
- "sdkName": "testEndpoint",
2097
- "summary": "Send a test webhook",
2098
- "tag": "Endpoints",
2099
- "tagCommand": "endpoints"
2100
- },
2101
- {
2102
- "binaryResponse": false,
2103
- "bodyRequired": true,
2104
- "command": "update-endpoint",
2105
- "description": "Updates an active webhook endpoint. If the URL is changed, the old\nendpoint is deactivated and a new one is created (or an existing\ndeactivated endpoint with the new URL is reactivated).\n",
2106
- "hasJsonBody": true,
2107
- "method": "PATCH",
2108
- "operationId": "updateEndpoint",
2109
- "path": "/endpoints/{id}",
2110
- "pathParams": [
2111
- {
2112
- "description": "Resource UUID",
2113
- "enum": null,
2114
- "name": "id",
2115
- "required": true,
2116
- "type": "string"
2117
- }
2118
- ],
2119
- "queryParams": [],
2120
- "requestSchema": {
2121
- "type": "object",
2122
- "additionalProperties": false,
2123
- "properties": {
2124
- "url": {
2125
- "type": "string",
2126
- "minLength": 1,
2127
- "description": "New webhook URL (triggers endpoint rotation)"
2128
- },
2129
- "enabled": {
2130
- "type": "boolean"
2131
- },
2132
- "domain_id": {
2133
- "type": [
2134
- "string",
2135
- "null"
2136
- ],
2137
- "format": "uuid"
2138
- },
2139
- "rules": {
2140
- "type": "object"
2141
- }
2142
- },
2143
- "minProperties": 1
2144
- },
2145
- "responseSchema": {
2146
- "type": "object",
2147
- "properties": {
2148
- "id": {
2149
- "type": "string",
2150
- "format": "uuid"
2151
- },
2152
- "org_id": {
2153
- "type": "string",
2154
- "format": "uuid"
2155
- },
2156
- "url": {
2157
- "type": [
2158
- "string",
2159
- "null"
2160
- ]
2161
- },
2162
- "enabled": {
2163
- "type": "boolean"
2164
- },
2165
- "domain_id": {
2166
- "type": [
2167
- "string",
2168
- "null"
2169
- ],
2170
- "format": "uuid",
2171
- "description": "Restrict this endpoint to emails from a specific domain"
2172
- },
2173
- "rules": {
2174
- "type": "object",
2175
- "description": "Endpoint-specific filtering rules"
2176
- },
2177
- "created_at": {
2178
- "type": "string",
2179
- "format": "date-time"
2180
- },
2181
- "updated_at": {
2182
- "type": "string",
2183
- "format": "date-time"
2184
- },
2185
- "delivery_count": {
2186
- "type": "integer",
2187
- "description": "Total webhook deliveries attempted"
2188
- },
2189
- "success_count": {
2190
- "type": "integer",
2191
- "description": "Successful deliveries"
2192
- },
2193
- "failure_count": {
2194
- "type": "integer",
2195
- "description": "Failed deliveries"
2196
- },
2197
- "consecutive_fails": {
2198
- "type": "integer",
2199
- "description": "Current streak of consecutive failures"
2200
- },
2201
- "last_delivery_at": {
2202
- "type": [
2203
- "string",
2204
- "null"
2205
- ],
2206
- "format": "date-time"
2207
- },
2208
- "last_success_at": {
2209
- "type": [
2210
- "string",
2211
- "null"
2212
- ],
2213
- "format": "date-time"
2214
- },
2215
- "last_failure_at": {
2216
- "type": [
2217
- "string",
2218
- "null"
2219
- ],
2220
- "format": "date-time"
2221
- },
2222
- "deactivated_at": {
2223
- "type": [
2224
- "string",
2225
- "null"
2226
- ],
2227
- "format": "date-time"
2228
- }
2229
- },
2230
- "required": [
2231
- "id",
2232
- "org_id",
2233
- "enabled",
2234
- "rules",
2235
- "created_at",
2236
- "updated_at",
2237
- "delivery_count",
2238
- "success_count",
2239
- "failure_count",
2240
- "consecutive_fails"
2241
- ]
2242
- },
2243
- "sdkName": "updateEndpoint",
2244
- "summary": "Update a webhook endpoint",
2245
- "tag": "Endpoints",
2246
- "tagCommand": "endpoints"
2247
- },
2248
- {
2249
- "binaryResponse": false,
2250
- "bodyRequired": true,
2251
- "command": "create-filter",
2252
- "description": "Creates a new whitelist or blocklist filter. Per-domain filters\nrequire a Pro plan. Patterns are stored as lowercase.\n",
2253
- "hasJsonBody": true,
2254
- "method": "POST",
2255
- "operationId": "createFilter",
2256
- "path": "/filters",
2257
- "pathParams": [],
2258
- "queryParams": [],
2259
- "requestSchema": {
2260
- "type": "object",
2261
- "additionalProperties": false,
2262
- "properties": {
2263
- "type": {
2264
- "type": "string",
2265
- "enum": [
2266
- "whitelist",
2267
- "blocklist"
2268
- ]
2269
- },
2270
- "pattern": {
2271
- "type": "string",
2272
- "minLength": 1,
2273
- "maxLength": 500,
2274
- "description": "Email address or pattern to filter"
2275
- },
2276
- "domain_id": {
2277
- "type": [
2278
- "string",
2279
- "null"
2280
- ],
2281
- "format": "uuid",
2282
- "description": "Restrict filter to a specific domain (Pro plan required)"
2283
- }
2284
- },
2285
- "required": [
2286
- "type",
2287
- "pattern"
2288
- ]
2289
- },
2290
- "responseSchema": {
2291
- "type": "object",
2292
- "properties": {
2293
- "id": {
2294
- "type": "string",
2295
- "format": "uuid"
2296
- },
2297
- "org_id": {
2298
- "type": "string",
2299
- "format": "uuid"
2300
- },
2301
- "domain_id": {
2302
- "type": [
2303
- "string",
2304
- "null"
2305
- ],
2306
- "format": "uuid",
2307
- "description": "If set, filter applies only to this domain"
2308
- },
2309
- "type": {
2310
- "type": "string",
2311
- "enum": [
2312
- "whitelist",
2313
- "blocklist"
2314
- ]
2315
- },
2316
- "pattern": {
2317
- "type": "string",
2318
- "description": "Email address or pattern to match (stored lowercase)"
2319
- },
2320
- "enabled": {
2321
- "type": "boolean"
2322
- },
2323
- "created_at": {
2324
- "type": "string",
2325
- "format": "date-time"
2326
- }
2327
- },
2328
- "required": [
2329
- "id",
2330
- "org_id",
2331
- "type",
2332
- "pattern",
2333
- "enabled",
2334
- "created_at"
2335
- ]
2336
- },
2337
- "sdkName": "createFilter",
2338
- "summary": "Create a filter rule",
2339
- "tag": "Filters",
2340
- "tagCommand": "filters"
2341
- },
2342
- {
2343
- "binaryResponse": false,
2344
- "bodyRequired": false,
2345
- "command": "delete-filter",
2346
- "description": null,
2347
- "hasJsonBody": false,
2348
- "method": "DELETE",
2349
- "operationId": "deleteFilter",
2350
- "path": "/filters/{id}",
2351
- "pathParams": [
2352
- {
2353
- "description": "Resource UUID",
2354
- "enum": null,
2355
- "name": "id",
2356
- "required": true,
2357
- "type": "string"
2358
- }
2359
- ],
2360
- "queryParams": [],
2361
- "requestSchema": null,
2362
- "responseSchema": null,
2363
- "sdkName": "deleteFilter",
2364
- "summary": "Delete a filter rule",
2365
- "tag": "Filters",
2366
- "tagCommand": "filters"
2367
- },
2368
- {
2369
- "binaryResponse": false,
2370
- "bodyRequired": false,
2371
- "command": "list-filters",
2372
- "description": "Returns all whitelist and blocklist filter rules.",
2373
- "hasJsonBody": false,
2374
- "method": "GET",
2375
- "operationId": "listFilters",
2376
- "path": "/filters",
2377
- "pathParams": [],
2378
- "queryParams": [],
2379
- "requestSchema": null,
2380
- "responseSchema": {
2381
- "type": "array",
2382
- "items": {
2383
- "type": "object",
2384
- "properties": {
2385
- "id": {
2386
- "type": "string",
2387
- "format": "uuid"
2388
- },
2389
- "org_id": {
2390
- "type": "string",
2391
- "format": "uuid"
2392
- },
2393
- "domain_id": {
2394
- "type": [
2395
- "string",
2396
- "null"
2397
- ],
2398
- "format": "uuid",
2399
- "description": "If set, filter applies only to this domain"
2400
- },
2401
- "type": {
2402
- "type": "string",
2403
- "enum": [
2404
- "whitelist",
2405
- "blocklist"
2406
- ]
2407
- },
2408
- "pattern": {
2409
- "type": "string",
2410
- "description": "Email address or pattern to match (stored lowercase)"
2411
- },
2412
- "enabled": {
2413
- "type": "boolean"
2414
- },
2415
- "created_at": {
2416
- "type": "string",
2417
- "format": "date-time"
2418
- }
2419
- },
2420
- "required": [
2421
- "id",
2422
- "org_id",
2423
- "type",
2424
- "pattern",
2425
- "enabled",
2426
- "created_at"
2427
- ]
2428
- }
2429
- },
2430
- "sdkName": "listFilters",
2431
- "summary": "List filter rules",
2432
- "tag": "Filters",
2433
- "tagCommand": "filters"
2434
- },
2435
- {
2436
- "binaryResponse": false,
2437
- "bodyRequired": true,
2438
- "command": "update-filter",
2439
- "description": "Toggle a filter's enabled state.",
2440
- "hasJsonBody": true,
2441
- "method": "PATCH",
2442
- "operationId": "updateFilter",
2443
- "path": "/filters/{id}",
2444
- "pathParams": [
2445
- {
2446
- "description": "Resource UUID",
2447
- "enum": null,
2448
- "name": "id",
2449
- "required": true,
2450
- "type": "string"
2451
- }
2452
- ],
2453
- "queryParams": [],
2454
- "requestSchema": {
2455
- "type": "object",
2456
- "additionalProperties": false,
2457
- "properties": {
2458
- "enabled": {
2459
- "type": "boolean"
2460
- }
2461
- },
2462
- "required": [
2463
- "enabled"
2464
- ]
2465
- },
2466
- "responseSchema": {
2467
- "type": "object",
2468
- "properties": {
2469
- "id": {
2470
- "type": "string",
2471
- "format": "uuid"
2472
- },
2473
- "org_id": {
2474
- "type": "string",
2475
- "format": "uuid"
2476
- },
2477
- "domain_id": {
2478
- "type": [
2479
- "string",
2480
- "null"
2481
- ],
2482
- "format": "uuid",
2483
- "description": "If set, filter applies only to this domain"
2484
- },
2485
- "type": {
2486
- "type": "string",
2487
- "enum": [
2488
- "whitelist",
2489
- "blocklist"
2490
- ]
2491
- },
2492
- "pattern": {
2493
- "type": "string",
2494
- "description": "Email address or pattern to match (stored lowercase)"
2495
- },
2496
- "enabled": {
2497
- "type": "boolean"
2498
- },
2499
- "created_at": {
2500
- "type": "string",
2501
- "format": "date-time"
2502
- }
2503
- },
2504
- "required": [
2505
- "id",
2506
- "org_id",
2507
- "type",
2508
- "pattern",
2509
- "enabled",
2510
- "created_at"
2511
- ]
2512
- },
2513
- "sdkName": "updateFilter",
2514
- "summary": "Update a filter rule",
2515
- "tag": "Filters",
2516
- "tagCommand": "filters"
2517
- },
2518
- {
2519
- "binaryResponse": false,
2520
- "bodyRequired": true,
2521
- "command": "create-function",
2522
- "description": "Creates and deploys a new function. The handler must be a single\nESM module whose default export is an object with an async\n`fetch(request, env)` method (Workers-style). The gateway\nHMAC-verifies the POST against the org's webhook secret before\ninvoking the handler; the request body parses to an\n`email.received` event (see `EmailReceivedEvent` and the\nWebhook payload section for the full schema). Code is bundled\nbefore being uploaded; ship a single self-contained file rather\nthan relying on external imports.\n\n**Code limits.** `code` is capped at 1 MiB UTF-8. `sourceMap`\n(optional) is capped at 5 MiB UTF-8 and is stored only on the\nedge runtime side; it is not persisted in Primitive's database.\n\n**Auto-wiring.** On successful deploy, Primitive automatically\ncreates a webhook endpoint that delivers inbound mail to the\nfunction. There is nothing to configure on the Endpoints API\nfor this to work; the gateway URL returned here is for\nreference only and is not directly callable from outside.\n\n**Secrets.** New functions ship with the managed secrets\n(`PRIMITIVE_WEBHOOK_SECRET`, `PRIMITIVE_API_KEY`) already\nbound. Add user-set secrets via\n`POST /functions/{id}/secrets`; secret writes only land in the\nrunning handler on the next redeploy.\n",
2523
- "hasJsonBody": true,
2524
- "method": "POST",
2525
- "operationId": "createFunction",
2526
- "path": "/functions",
2527
- "pathParams": [],
2528
- "queryParams": [],
2529
- "requestSchema": {
2530
- "type": "object",
2531
- "additionalProperties": false,
2532
- "properties": {
2533
- "name": {
2534
- "type": "string",
2535
- "pattern": "^[a-z0-9_-]{1,64}$",
2536
- "description": "Slug-style name. Lowercase letters, digits, hyphens, and\nunderscores. 1 to 64 characters. Must be unique within the\norg; a 409 is returned on collision.\n"
2537
- },
2538
- "code": {
2539
- "type": "string",
2540
- "minLength": 1,
2541
- "maxLength": 1048576,
2542
- "description": "Bundled handler as a single ESM module. Up to 1 MiB UTF-8.\nMust export a default `{ async fetch(req, env, ctx) { ... } }`\nobject.\n"
2543
- },
2544
- "sourceMap": {
2545
- "type": "string",
2546
- "minLength": 1,
2547
- "maxLength": 5242880,
2548
- "description": "Optional source map for the bundle. Up to 5 MiB UTF-8.\nStored only on the runtime side (not in Primitive's\ndatabase) and used to symbolicate stack traces in the\nfunction's logs.\n"
2549
- }
2550
- },
2551
- "required": [
2552
- "name",
2553
- "code"
2554
- ]
2555
- },
2556
- "responseSchema": {
2557
- "type": "object",
2558
- "description": "Returned by POST /functions on a successful deploy.",
2559
- "properties": {
2560
- "id": {
2561
- "type": "string",
2562
- "format": "uuid"
2563
- },
2564
- "name": {
2565
- "type": "string"
2566
- },
2567
- "deploy_status": {
2568
- "type": "string",
2569
- "enum": [
2570
- "pending",
2571
- "deployed",
2572
- "failed"
2573
- ],
2574
- "description": "Lifecycle state of the latest deploy attempt:\n * `pending` — deploy in flight; the runtime has not yet\n confirmed the new bundle is live.\n * `deployed` — the running edge handler is the latest code.\n * `failed` — the most recent deploy attempt failed; the\n previously-live code (if any) is still running. The\n `deploy_error` field carries the error message.\n"
2575
- },
2576
- "gateway_url": {
2577
- "type": "string",
2578
- "format": "uri"
2579
- }
2580
- },
2581
- "required": [
2582
- "id",
2583
- "name",
2584
- "deploy_status",
2585
- "gateway_url"
2586
- ]
2587
- },
2588
- "sdkName": "createFunction",
2589
- "summary": "Deploy a function",
2590
- "tag": "Functions",
2591
- "tagCommand": "functions"
2592
- },
2593
- {
2594
- "binaryResponse": false,
2595
- "bodyRequired": true,
2596
- "command": "create-function-secret",
2597
- "description": "Idempotent insert-or-update keyed on `(function_id, key)`.\nReturns 201 the first time the key is set, 200 on subsequent\nupdates. Values are encrypted at rest and only become visible\nto the running handler on the next deploy (`PUT /functions/{id}`\nwith the existing code is sufficient to refresh bindings).\n\nKeys must match `^[A-Z_][A-Z0-9_]*$` (uppercase letters,\ndigits, underscores; first character is a letter or\nunderscore). Values are at most 4096 UTF-8 bytes. System-\nmanaged keys are reserved and rejected.\n",
2598
- "hasJsonBody": true,
2599
- "method": "POST",
2600
- "operationId": "createFunctionSecret",
2601
- "path": "/functions/{id}/secrets",
2602
- "pathParams": [
2603
- {
2604
- "description": "Resource UUID",
2605
- "enum": null,
2606
- "name": "id",
2607
- "required": true,
2608
- "type": "string"
2609
- }
2610
- ],
2611
- "queryParams": [],
2612
- "requestSchema": {
2613
- "type": "object",
2614
- "additionalProperties": false,
2615
- "description": "Body for POST /functions/{id}/secrets.",
2616
- "properties": {
2617
- "key": {
2618
- "type": "string",
2619
- "pattern": "^[A-Z_][A-Z0-9_]*$",
2620
- "description": "Uppercase letters, digits, and underscores. Must start with\na letter or underscore. System-managed keys (e.g.\nPRIMITIVE_WEBHOOK_SECRET) are reserved.\n"
2621
- },
2622
- "value": {
2623
- "type": "string",
2624
- "minLength": 1,
2625
- "maxLength": 4096,
2626
- "description": "Secret value, up to 4096 UTF-8 bytes. Encrypted at rest.\nNever returned by any read endpoint.\n"
2627
- }
2628
- },
2629
- "required": [
2630
- "key",
2631
- "value"
2632
- ]
2633
- },
2634
- "responseSchema": {
2635
- "type": "object",
2636
- "description": "Returned by POST and PUT secret routes.",
2637
- "properties": {
2638
- "key": {
2639
- "type": "string"
2640
- },
2641
- "created_at": {
2642
- "type": "string",
2643
- "format": "date-time"
2644
- },
2645
- "updated_at": {
2646
- "type": "string",
2647
- "format": "date-time"
2648
- },
2649
- "created": {
2650
- "type": "boolean",
2651
- "description": "True if this call inserted a new row, false if it updated an existing one."
2652
- }
2653
- },
2654
- "required": [
2655
- "key",
2656
- "created_at",
2657
- "updated_at",
2658
- "created"
2659
- ]
2660
- },
2661
- "sdkName": "createFunctionSecret",
2662
- "summary": "Create or update a secret",
2663
- "tag": "Functions",
2664
- "tagCommand": "functions"
2665
- },
2666
- {
2667
- "binaryResponse": false,
2668
- "bodyRequired": false,
2669
- "command": "delete-function",
2670
- "description": "Soft-deletes the function row, removes the script from the edge\nruntime, and deactivates the auto-wired webhook endpoint so no\nfurther inbound mail is delivered. Past deploy history,\ninvocations, and logs are retained.\n\nReturns 502 if the runtime delete fails partway; the function\nrow stays in place and the call is safe to retry until it\nsucceeds.\n",
2671
- "hasJsonBody": false,
2672
- "method": "DELETE",
2673
- "operationId": "deleteFunction",
2674
- "path": "/functions/{id}",
2675
- "pathParams": [
2676
- {
2677
- "description": "Resource UUID",
2678
- "enum": null,
2679
- "name": "id",
2680
- "required": true,
2681
- "type": "string"
2682
- }
2683
- ],
2684
- "queryParams": [],
2685
- "requestSchema": null,
2686
- "responseSchema": null,
2687
- "sdkName": "deleteFunction",
2688
- "summary": "Delete a function",
2689
- "tag": "Functions",
2690
- "tagCommand": "functions"
2691
- },
2692
- {
2693
- "binaryResponse": false,
2694
- "bodyRequired": false,
2695
- "command": "delete-function-secret",
2696
- "description": "Removes the secret. The binding stays live in the running\nhandler until the next deploy refreshes the binding set\n(`PUT /functions/{id}` with the existing code is sufficient).\nReturns 404 if the key did not exist. Managed system keys\ncannot be deleted.\n",
2697
- "hasJsonBody": false,
2698
- "method": "DELETE",
2699
- "operationId": "deleteFunctionSecret",
2700
- "path": "/functions/{id}/secrets/{key}",
2701
- "pathParams": [
2702
- {
2703
- "description": "Resource UUID",
2704
- "enum": null,
2705
- "name": "id",
2706
- "required": true,
2707
- "type": "string"
2708
- },
2709
- {
2710
- "description": "Secret key. Must match `^[A-Z_][A-Z0-9_]*$`.",
2711
- "enum": null,
2712
- "name": "key",
2713
- "required": true,
2714
- "type": "string"
2715
- }
2716
- ],
2717
- "queryParams": [],
2718
- "requestSchema": null,
2719
- "responseSchema": null,
2720
- "sdkName": "deleteFunctionSecret",
2721
- "summary": "Delete a secret",
2722
- "tag": "Functions",
2723
- "tagCommand": "functions"
2724
- },
2725
- {
2726
- "binaryResponse": false,
2727
- "bodyRequired": false,
2728
- "command": "get-function",
2729
- "description": "Returns the full record for a function, including its current\nsource code and the deploy status / error from the most recent\ndeploy attempt.\n",
2730
- "hasJsonBody": false,
2731
- "method": "GET",
2732
- "operationId": "getFunction",
2733
- "path": "/functions/{id}",
2734
- "pathParams": [
2735
- {
2736
- "description": "Resource UUID",
2737
- "enum": null,
2738
- "name": "id",
2739
- "required": true,
2740
- "type": "string"
2741
- }
2742
- ],
2743
- "queryParams": [],
2744
- "requestSchema": null,
2745
- "responseSchema": {
2746
- "type": "object",
2747
- "description": "Full function record returned by GET / PUT.",
2748
- "properties": {
2749
- "id": {
2750
- "type": "string",
2751
- "format": "uuid"
2752
- },
2753
- "name": {
2754
- "type": "string"
2755
- },
2756
- "code": {
2757
- "type": "string",
2758
- "description": "The bundled handler source. UTF-8 string up to 1 MiB. The\nsame value most recently passed as `code` to POST or PUT.\n"
2759
- },
2760
- "deploy_status": {
2761
- "type": "string",
2762
- "enum": [
2763
- "pending",
2764
- "deployed",
2765
- "failed"
2766
- ],
2767
- "description": "Lifecycle state of the latest deploy attempt:\n * `pending` — deploy in flight; the runtime has not yet\n confirmed the new bundle is live.\n * `deployed` — the running edge handler is the latest code.\n * `failed` — the most recent deploy attempt failed; the\n previously-live code (if any) is still running. The\n `deploy_error` field carries the error message.\n"
2768
- },
2769
- "deploy_error": {
2770
- "type": [
2771
- "string",
2772
- "null"
2773
- ],
2774
- "description": "Error message from the most recent failed deploy, or null\nafter a successful deploy. Surface this to users to explain\na `failed` status without polling.\n"
2775
- },
2776
- "deployed_at": {
2777
- "type": [
2778
- "string",
2779
- "null"
2780
- ],
2781
- "format": "date-time"
2782
- },
2783
- "gateway_url": {
2784
- "type": "string",
2785
- "format": "uri"
2786
- },
2787
- "created_at": {
2788
- "type": "string",
2789
- "format": "date-time"
2790
- },
2791
- "updated_at": {
2792
- "type": "string",
2793
- "format": "date-time"
2794
- }
2795
- },
2796
- "required": [
2797
- "id",
2798
- "name",
2799
- "code",
2800
- "deploy_status",
2801
- "gateway_url",
2802
- "created_at",
2803
- "updated_at"
2804
- ]
2805
- },
2806
- "sdkName": "getFunction",
2807
- "summary": "Get a function",
2808
- "tag": "Functions",
2809
- "tagCommand": "functions"
2810
- },
2811
- {
2812
- "binaryResponse": false,
2813
- "bodyRequired": false,
2814
- "command": "list-function-logs",
2815
- "description": "Returns the most recent `function_logs` rows for the function,\nnewest first. Each row is a single `console.log` / `console.error`\ninvocation captured from the running handler.\n\nPage through history with the opaque `cursor` returned as\n`next_cursor`; pass it back as the `cursor` query param on the\nnext call. `next_cursor` is `null` when there are no further\nrows. The cursor format is an implementation detail and should\nnot be parsed by callers.\n",
2816
- "hasJsonBody": false,
2817
- "method": "GET",
2818
- "operationId": "listFunctionLogs",
2819
- "path": "/functions/{id}/logs",
2820
- "pathParams": [
2821
- {
2822
- "description": "Resource UUID",
2823
- "enum": null,
2824
- "name": "id",
2825
- "required": true,
2826
- "type": "string"
2827
- }
2828
- ],
2829
- "queryParams": [
2830
- {
2831
- "description": "Maximum number of rows to return. Clamped to 1..200; default\n50.\n",
2832
- "enum": null,
2833
- "name": "limit",
2834
- "required": false,
2835
- "type": "integer"
2836
- },
2837
- {
2838
- "description": "Opaque pagination cursor from a previous response's\n`next_cursor`. Omit on the first call.\n",
2839
- "enum": null,
2840
- "name": "cursor",
2841
- "required": false,
2842
- "type": "string"
2843
- }
2844
- ],
2845
- "requestSchema": null,
2846
- "responseSchema": {
2847
- "type": "object",
2848
- "properties": {
2849
- "items": {
2850
- "type": "array",
2851
- "items": {
2852
- "type": "object",
2853
- "description": "One row from GET /functions/{id}/logs. Represents a single\ncaptured log line emitted by the running handler (e.g. via\n`console.log` / `console.error`).\n",
2854
- "properties": {
2855
- "id": {
2856
- "type": "string",
2857
- "format": "uuid",
2858
- "description": "Unique log row id (stable across pages)."
2859
- },
2860
- "function_id": {
2861
- "type": "string",
2862
- "format": "uuid",
2863
- "description": "The function this log row belongs to."
2864
- },
2865
- "level": {
2866
- "type": "string",
2867
- "enum": [
2868
- "debug",
2869
- "log",
2870
- "info",
2871
- "warn",
2872
- "error"
2873
- ],
2874
- "description": "Severity. `log` is the runtime's default for unannotated\n`console.log` calls; the other levels match standard\n`console.*` methods.\n"
2875
- },
2876
- "message": {
2877
- "type": "string",
2878
- "description": "The textual message body. The runtime stringifies non-string\narguments before persisting, so this is always a plain\nstring.\n"
2879
- },
2880
- "ts": {
2881
- "type": "string",
2882
- "format": "date-time",
2883
- "description": "When the handler emitted this line. Newest-first ordering\non this column drives pagination; clock is the runtime's,\nnot the gateway's.\n"
2884
- },
2885
- "metadata": {
2886
- "type": [
2887
- "object",
2888
- "null"
2889
- ],
2890
- "additionalProperties": true,
2891
- "description": "Optional structured payload the runtime attaches alongside\nthe message (e.g. extra args passed to `console.log`).\nShape is opaque; treat keys as untyped.\n"
2892
- }
2893
- },
2894
- "required": [
2895
- "id",
2896
- "function_id",
2897
- "level",
2898
- "message",
2899
- "ts"
2900
- ]
2901
- }
2902
- },
2903
- "next_cursor": {
2904
- "type": [
2905
- "string",
2906
- "null"
2907
- ],
2908
- "description": "Pass back as `cursor` to fetch the next\npage. `null` when no further rows exist.\n"
2909
- }
2910
- },
2911
- "required": [
2912
- "items",
2913
- "next_cursor"
2914
- ]
2915
- },
2916
- "sdkName": "listFunctionLogs",
2917
- "summary": "List a function's execution logs",
2918
- "tag": "Functions",
2919
- "tagCommand": "functions"
2920
- },
2921
- {
2922
- "binaryResponse": false,
2923
- "bodyRequired": false,
2924
- "command": "list-function-secrets",
2925
- "description": "Returns metadata for every secret bound to the function, with\nmanaged entries (provisioned by Primitive) listed first and\nuser-set entries listed alphabetically after. **Values are\nnever returned.** Secret writes are write-only.\n\nManaged entries (e.g. `PRIMITIVE_WEBHOOK_SECRET`,\n`PRIMITIVE_API_KEY`) carry a `description` instead of\n`created_at` / `updated_at`. They cannot be created, updated,\nor deleted via this API.\n",
2926
- "hasJsonBody": false,
2927
- "method": "GET",
2928
- "operationId": "listFunctionSecrets",
2929
- "path": "/functions/{id}/secrets",
2930
- "pathParams": [
2931
- {
2932
- "description": "Resource UUID",
2933
- "enum": null,
2934
- "name": "id",
2935
- "required": true,
2936
- "type": "string"
2937
- }
2938
- ],
2939
- "queryParams": [],
2940
- "requestSchema": null,
2941
- "responseSchema": {
2942
- "type": "object",
2943
- "properties": {
2944
- "items": {
2945
- "type": "array",
2946
- "items": {
2947
- "type": "object",
2948
- "description": "One row from GET /functions/{id}/secrets. Discriminate on the\n`managed` field:\n * `managed = true` — system secret provisioned by Primitive.\n `description` is set; `created_at` / `updated_at` are\n null because the row is virtual (resolved at deploy time\n from the managed registry, not stored in the secrets\n table).\n * `managed = false` — secret the user set via the API.\n `created_at` / `updated_at` are set; `description` is\n null.\n",
2949
- "properties": {
2950
- "key": {
2951
- "type": "string"
2952
- },
2953
- "managed": {
2954
- "type": "boolean",
2955
- "description": "True for managed system secrets, false for user-set entries."
2956
- },
2957
- "description": {
2958
- "type": [
2959
- "string",
2960
- "null"
2961
- ],
2962
- "description": "Set on managed entries only; null on user-set entries."
2963
- },
2964
- "created_at": {
2965
- "type": [
2966
- "string",
2967
- "null"
2968
- ],
2969
- "format": "date-time",
2970
- "description": "Set on user-set entries only; null on managed entries."
2971
- },
2972
- "updated_at": {
2973
- "type": [
2974
- "string",
2975
- "null"
2976
- ],
2977
- "format": "date-time",
2978
- "description": "Set on user-set entries only; null on managed entries."
2979
- }
2980
- },
2981
- "required": [
2982
- "key",
2983
- "managed"
2984
- ]
2985
- }
2986
- }
2987
- },
2988
- "required": [
2989
- "items"
2990
- ]
2991
- },
2992
- "sdkName": "listFunctionSecrets",
2993
- "summary": "List a function's secrets",
2994
- "tag": "Functions",
2995
- "tagCommand": "functions"
2996
- },
2997
- {
2998
- "binaryResponse": false,
2999
- "bodyRequired": false,
3000
- "command": "list-functions",
3001
- "description": "Returns every active (non-deleted) function in the org, newest\nfirst. Each entry carries the deploy status and the gateway URL\nthat the platform's webhook delivery loop posts to. To inspect\nthe source code or deploy errors, use `GET /functions/{id}`.\n",
3002
- "hasJsonBody": false,
3003
- "method": "GET",
3004
- "operationId": "listFunctions",
3005
- "path": "/functions",
3006
- "pathParams": [],
3007
- "queryParams": [],
3008
- "requestSchema": null,
3009
- "responseSchema": {
3010
- "type": "array",
3011
- "items": {
3012
- "type": "object",
3013
- "description": "One row from the function listing.",
3014
- "properties": {
3015
- "id": {
3016
- "type": "string",
3017
- "format": "uuid",
3018
- "description": "Function id, also the script name in the edge runtime."
3019
- },
3020
- "name": {
3021
- "type": "string",
3022
- "description": "Slug-style name set on creation. Stable; cannot be changed."
3023
- },
3024
- "deploy_status": {
3025
- "type": "string",
3026
- "enum": [
3027
- "pending",
3028
- "deployed",
3029
- "failed"
3030
- ],
3031
- "description": "Lifecycle state of the latest deploy attempt:\n * `pending` — deploy in flight; the runtime has not yet\n confirmed the new bundle is live.\n * `deployed` — the running edge handler is the latest code.\n * `failed` — the most recent deploy attempt failed; the\n previously-live code (if any) is still running. The\n `deploy_error` field carries the error message.\n"
3032
- },
3033
- "deployed_at": {
3034
- "type": [
3035
- "string",
3036
- "null"
3037
- ],
3038
- "format": "date-time",
3039
- "description": "Timestamp of the most recent successful deploy. Null until the first deploy succeeds."
3040
- },
3041
- "gateway_url": {
3042
- "type": "string",
3043
- "format": "uri",
3044
- "description": "URL the platform's webhook delivery loop posts to in order\nto invoke the function. Reference only; not directly\ncallable from outside.\n"
3045
- },
3046
- "created_at": {
3047
- "type": "string",
3048
- "format": "date-time"
3049
- },
3050
- "updated_at": {
3051
- "type": "string",
3052
- "format": "date-time"
3053
- }
3054
- },
3055
- "required": [
3056
- "id",
3057
- "name",
3058
- "deploy_status",
3059
- "gateway_url",
3060
- "created_at",
3061
- "updated_at"
3062
- ]
3063
- }
3064
- },
3065
- "sdkName": "listFunctions",
3066
- "summary": "List functions",
3067
- "tag": "Functions",
3068
- "tagCommand": "functions"
3069
- },
3070
- {
3071
- "binaryResponse": false,
3072
- "bodyRequired": true,
3073
- "command": "set-function-secret",
3074
- "description": "Path-keyed companion to `POST /functions/{id}/secrets`.\nIdempotent: returns 201 the first time the key is set, 200 on\nsubsequent updates. Same validation rules and same write-only\nguarantees as the POST verb; the new value lands in the running\nhandler on the next deploy.\n",
3075
- "hasJsonBody": true,
3076
- "method": "PUT",
3077
- "operationId": "setFunctionSecret",
3078
- "path": "/functions/{id}/secrets/{key}",
3079
- "pathParams": [
3080
- {
3081
- "description": "Resource UUID",
3082
- "enum": null,
3083
- "name": "id",
3084
- "required": true,
3085
- "type": "string"
3086
- },
3087
- {
3088
- "description": "Secret key. Must match `^[A-Z_][A-Z0-9_]*$`.",
3089
- "enum": null,
3090
- "name": "key",
3091
- "required": true,
3092
- "type": "string"
3093
- }
3094
- ],
3095
- "queryParams": [],
3096
- "requestSchema": {
3097
- "type": "object",
3098
- "additionalProperties": false,
3099
- "description": "Body for PUT /functions/{id}/secrets/{key}. Key comes from the path.",
3100
- "properties": {
3101
- "value": {
3102
- "type": "string",
3103
- "minLength": 1,
3104
- "maxLength": 4096
3105
- }
3106
- },
3107
- "required": [
3108
- "value"
3109
- ]
3110
- },
3111
- "responseSchema": {
3112
- "type": "object",
3113
- "description": "Returned by POST and PUT secret routes.",
3114
- "properties": {
3115
- "key": {
3116
- "type": "string"
3117
- },
3118
- "created_at": {
3119
- "type": "string",
3120
- "format": "date-time"
3121
- },
3122
- "updated_at": {
3123
- "type": "string",
3124
- "format": "date-time"
3125
- },
3126
- "created": {
3127
- "type": "boolean",
3128
- "description": "True if this call inserted a new row, false if it updated an existing one."
3129
- }
3130
- },
3131
- "required": [
3132
- "key",
3133
- "created_at",
3134
- "updated_at",
3135
- "created"
3136
- ]
3137
- },
3138
- "sdkName": "setFunctionSecret",
3139
- "summary": "Set a secret by key",
3140
- "tag": "Functions",
3141
- "tagCommand": "functions"
3142
- },
3143
- {
3144
- "binaryResponse": false,
3145
- "bodyRequired": false,
3146
- "command": "test-function",
3147
- "description": "Sends a real test email from a Primitive-controlled sender to a\nlocal-part on one of the org's verified inbound domains. By\ndefault the recipient is a synthetic\n`__primitive_function_test+<random>@<domain>` address that\nevery handler's catch-all routing receives identically; pass\n`local_part` to override and exercise routing logic that\nbranches on a specific recipient (the common pattern when one\nfunction handles multiple inboxes like `summarize@` and\n`action@`). The function fires through the normal MX delivery\npath, so reply / send-mail calls from inside the handler\nagainst the inbound's `email.id` work the same as in\nproduction. Returns immediately after the send is queued; the\ninvocation appears on the function's invocations list within a\nfew seconds.\n\nRequires that the function is currently `deployed`. Returns 422\nif the function is in `pending` or `failed` state, or if the\norg has no verified inbound domain to receive the test mail.\nReturns 400 if `local_part` is set to a value that does not\nmatch the local-part character set.\n",
3148
- "hasJsonBody": true,
3149
- "method": "POST",
3150
- "operationId": "testFunction",
3151
- "path": "/functions/{id}/test",
3152
- "pathParams": [
3153
- {
3154
- "description": "Resource UUID",
3155
- "enum": null,
3156
- "name": "id",
3157
- "required": true,
3158
- "type": "string"
3159
- }
3160
- ],
3161
- "queryParams": [],
3162
- "requestSchema": {
3163
- "type": "object",
3164
- "properties": {
3165
- "local_part": {
3166
- "type": "string",
3167
- "description": "Override the synthetic local-part. When set, the\ntest email is sent to `<local_part>@<picked-domain>`\ninstead of the default\n`__primitive_function_test+<random>@<picked-domain>`.\nMust start with an alphanumeric and contain only\nletters, digits, dots, plus signs, hyphens, or\nunderscores; 1-64 characters total.\n",
3168
- "minLength": 1,
3169
- "maxLength": 64,
3170
- "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$",
3171
- "example": "summarize"
3172
- }
3173
- }
3174
- },
3175
- "responseSchema": {
3176
- "type": "object",
3177
- "description": "Metadata returned by POST /functions/{id}/test. The send is\nqueued; the actual invocation lands on the function's\ninvocations list a few seconds later as the inbound mail\ntraverses the MX path.\n",
3178
- "properties": {
3179
- "inbound_domain": {
3180
- "type": "string",
3181
- "description": "Verified inbound domain the test email was sent to."
3182
- },
3183
- "to": {
3184
- "type": "string",
3185
- "description": "Synthetic local-part plus inbound_domain. Visible in the org's inbox."
3186
- },
3187
- "from": {
3188
- "type": "string",
3189
- "description": "Primitive-controlled outbound sender used for the test."
3190
- },
3191
- "send_id": {
3192
- "type": "string",
3193
- "description": "Outbound message id from the underlying send. NOT the\ninbound email's id; the inbound id is created when the\nemail arrives via MX and lands on the function's\ninvocations list.\n"
3194
- },
3195
- "subject": {
3196
- "type": "string",
3197
- "description": "Subject placed on the test email so it can be located in the inbox."
3198
- },
3199
- "poll_since": {
3200
- "type": "string",
3201
- "format": "date-time",
3202
- "description": "ISO timestamp suitable as a `since` lower bound when\npolling /emails for the inbound's arrival. Captured\nslightly before the send to absorb light clock skew.\n"
3203
- },
3204
- "watch_url": {
3205
- "type": "string",
3206
- "format": "uri",
3207
- "description": "Function detail page where invocations show up live."
3208
- }
3209
- },
3210
- "required": [
3211
- "inbound_domain",
3212
- "to",
3213
- "from",
3214
- "send_id",
3215
- "subject",
3216
- "poll_since",
3217
- "watch_url"
3218
- ]
3219
- },
3220
- "sdkName": "testFunction",
3221
- "summary": "Send a test invocation",
3222
- "tag": "Functions",
3223
- "tagCommand": "functions"
3224
- },
3225
- {
3226
- "binaryResponse": false,
3227
- "bodyRequired": true,
3228
- "command": "update-function",
3229
- "description": "Replaces the function's source code with the body's `code` and\ntriggers a redeploy. Same size limits as `POST /functions`.\nUse this verb to push secret writes into the running handler:\npassing the same `code` re-runs the deploy and refreshes the\nbinding set with the latest values from the secrets table.\n\nOn a 502 deploy failure, the previously-deployed code stays\nlive; the runtime never serves a half-built bundle. The\n`deploy_error` field on the returned record carries the error\nthat came back from the runtime so you can surface it to users\nwithout polling.\n",
3230
- "hasJsonBody": true,
3231
- "method": "PUT",
3232
- "operationId": "updateFunction",
3233
- "path": "/functions/{id}",
3234
- "pathParams": [
3235
- {
3236
- "description": "Resource UUID",
3237
- "enum": null,
3238
- "name": "id",
3239
- "required": true,
3240
- "type": "string"
3241
- }
3242
- ],
3243
- "queryParams": [],
3244
- "requestSchema": {
3245
- "type": "object",
3246
- "additionalProperties": false,
3247
- "properties": {
3248
- "code": {
3249
- "type": "string",
3250
- "minLength": 1,
3251
- "maxLength": 1048576,
3252
- "description": "New bundled handler. Same rules as CreateFunctionInput.code."
3253
- },
3254
- "sourceMap": {
3255
- "type": "string",
3256
- "minLength": 1,
3257
- "maxLength": 5242880
3258
- }
3259
- },
3260
- "required": [
3261
- "code"
3262
- ]
3263
- },
3264
- "responseSchema": {
3265
- "type": "object",
3266
- "description": "Full function record returned by GET / PUT.",
3267
- "properties": {
3268
- "id": {
3269
- "type": "string",
3270
- "format": "uuid"
3271
- },
3272
- "name": {
3273
- "type": "string"
3274
- },
3275
- "code": {
3276
- "type": "string",
3277
- "description": "The bundled handler source. UTF-8 string up to 1 MiB. The\nsame value most recently passed as `code` to POST or PUT.\n"
3278
- },
3279
- "deploy_status": {
3280
- "type": "string",
3281
- "enum": [
3282
- "pending",
3283
- "deployed",
3284
- "failed"
3285
- ],
3286
- "description": "Lifecycle state of the latest deploy attempt:\n * `pending` — deploy in flight; the runtime has not yet\n confirmed the new bundle is live.\n * `deployed` — the running edge handler is the latest code.\n * `failed` — the most recent deploy attempt failed; the\n previously-live code (if any) is still running. The\n `deploy_error` field carries the error message.\n"
3287
- },
3288
- "deploy_error": {
3289
- "type": [
3290
- "string",
3291
- "null"
3292
- ],
3293
- "description": "Error message from the most recent failed deploy, or null\nafter a successful deploy. Surface this to users to explain\na `failed` status without polling.\n"
3294
- },
3295
- "deployed_at": {
3296
- "type": [
3297
- "string",
3298
- "null"
3299
- ],
3300
- "format": "date-time"
3301
- },
3302
- "gateway_url": {
3303
- "type": "string",
3304
- "format": "uri"
3305
- },
3306
- "created_at": {
3307
- "type": "string",
3308
- "format": "date-time"
3309
- },
3310
- "updated_at": {
3311
- "type": "string",
3312
- "format": "date-time"
3313
- }
3314
- },
3315
- "required": [
3316
- "id",
3317
- "name",
3318
- "code",
3319
- "deploy_status",
3320
- "gateway_url",
3321
- "created_at",
3322
- "updated_at"
3323
- ]
3324
- },
3325
- "sdkName": "updateFunction",
3326
- "summary": "Update and redeploy a function",
3327
- "tag": "Functions",
3328
- "tagCommand": "functions"
3329
- },
3330
- {
3331
- "binaryResponse": false,
3332
- "bodyRequired": false,
3333
- "command": "get-send-permissions",
3334
- "description": "Returns a flat list of rules describing every recipient the\ncaller may send to. Each rule has a `type`, a kind-specific\npayload, and a human-readable `description`. If any rule\nmatches the recipient, /send-mail will accept the send under\nthe recipient-scope check.\n\nThe endpoint is the answer to \"where can I send\" without\nexposing internal entitlement names. Agents that don't\nrecognize a `type` can still read the `description` prose\nand act on it.\n\nRule kinds, ordered broadest-first so an agent can stop\nscanning at the first match:\n\n 1. `any_recipient` (one entry, only when the org can send\n anywhere): every other rule below it is redundant.\n 2. `managed_zone` (always emitted, one per Primitive-managed\n zone): sends to any address at *.primitive.email or\n *.email.works always succeed; no entitlement required.\n 3. `your_domain` (one per active verified outbound domain\n owned by the org): sends to that domain are approved.\n 4. `address` (one per address that has authenticated\n inbound mail to the org, capped at `meta.address_cap`):\n sends to that exact address are approved.\n\nThe list is informational, not an authorization check.\n/send-mail remains the source of truth on whether an\nindividual send will succeed (it also enforces the\nfrom-address and the `send_mail` entitlement, which are\nnot recipient-scope concerns and are not represented here).\n",
3335
- "hasJsonBody": false,
3336
- "method": "GET",
3337
- "operationId": "getSendPermissions",
3338
- "path": "/send-permissions",
3339
- "pathParams": [],
3340
- "queryParams": [],
3341
- "requestSchema": null,
3342
- "responseSchema": {
3343
- "type": "array",
3344
- "items": {
3345
- "description": "One recipient-scope rule describing a destination the caller\nmay send to. Discriminated on `type`. Each rule carries a\nhuman-prose `description` field intended for display.\n\nRule kinds are stable within an SDK release. A response\ncontaining a `type` value not enumerated in this schema\nmeans the server is running a newer version than the SDK;\nupgrade the SDK to the release that matches the server's\nschema. Strict-parsing SDKs (Go, Python) will raise a\ndecode error in that case rather than silently dropping\nthe unknown rule, since silent drops would let an outbound\nagent reason from an incomplete view of its own permissions.\n",
3346
- "discriminator": {
3347
- "propertyName": "type",
3348
- "mapping": {
3349
- "any_recipient": "#/components/schemas/SendPermissionAnyRecipient",
3350
- "managed_zone": "#/components/schemas/SendPermissionManagedZone",
3351
- "your_domain": "#/components/schemas/SendPermissionYourDomain",
3352
- "address": "#/components/schemas/SendPermissionAddress"
3353
- }
3354
- },
3355
- "oneOf": [
3356
- {
3357
- "type": "object",
3358
- "description": "The caller can send to any recipient. When this rule is\npresent, every other rule in the response is redundant.\n",
3359
- "properties": {
3360
- "type": {
3361
- "type": "string",
3362
- "enum": [
3363
- "any_recipient"
3364
- ]
3365
- },
3366
- "description": {
3367
- "type": "string",
3368
- "description": "Human-prose summary of the rule."
3369
- }
3370
- },
3371
- "required": [
3372
- "type",
3373
- "description"
3374
- ]
3375
- },
3376
- {
3377
- "type": "object",
3378
- "description": "The caller can send to any address at the named\nPrimitive-managed zone. Always emitted (no entitlement\nrequired) because Primitive owns the zone and every mailbox\nbelongs to a Primitive customer by construction.\n",
3379
- "properties": {
3380
- "type": {
3381
- "type": "string",
3382
- "enum": [
3383
- "managed_zone"
3384
- ]
3385
- },
3386
- "zone": {
3387
- "type": "string",
3388
- "description": "The managed apex domain. Sends are accepted to any\naddress at the apex itself or any subdomain (e.g.\n`alice@primitive.email` and `alice@acme.primitive.email`\nboth match the `primitive.email` zone rule).\n"
3389
- },
3390
- "description": {
3391
- "type": "string",
3392
- "description": "Human-prose summary of the rule."
3393
- }
3394
- },
3395
- "required": [
3396
- "type",
3397
- "zone",
3398
- "description"
3399
- ]
3400
- },
3401
- {
3402
- "type": "object",
3403
- "description": "The caller can send to any address at one of their own\nverified outbound domains. Emitted once per active row in\nthe org's `domains` table.\n",
3404
- "properties": {
3405
- "type": {
3406
- "type": "string",
3407
- "enum": [
3408
- "your_domain"
3409
- ]
3410
- },
3411
- "domain": {
3412
- "type": "string",
3413
- "description": "A verified outbound domain owned by the caller's org."
3414
- },
3415
- "description": {
3416
- "type": "string",
3417
- "description": "Human-prose summary of the rule."
3418
- }
3419
- },
3420
- "required": [
3421
- "type",
3422
- "domain",
3423
- "description"
3424
- ]
3425
- },
3426
- {
3427
- "type": "object",
3428
- "description": "The caller can send to a specific address that has\nauthenticated inbound mail to the org. Emitted once per row\nin the org's `known_send_addresses` table, capped at\n`meta.address_cap`.\n",
3429
- "properties": {
3430
- "type": {
3431
- "type": "string",
3432
- "enum": [
3433
- "address"
3434
- ]
3435
- },
3436
- "address": {
3437
- "type": "string",
3438
- "description": "The bare email address this rule grants sends to."
3439
- },
3440
- "last_received_at": {
3441
- "type": "string",
3442
- "format": "date-time",
3443
- "description": "Most recent inbound email from this address that\nauthenticated successfully (DMARC pass + DKIM/SPF\nalignment). Updated on each new authenticated receipt.\n"
3444
- },
3445
- "received_count": {
3446
- "type": "integer",
3447
- "description": "Total number of authenticated inbound emails from this\naddress. Increments only when `last_received_at` advances.\n"
3448
- },
3449
- "description": {
3450
- "type": "string",
3451
- "description": "Human-prose summary of the rule."
3452
- }
3453
- },
3454
- "required": [
3455
- "type",
3456
- "address",
3457
- "last_received_at",
3458
- "received_count",
3459
- "description"
3460
- ]
3461
- }
3462
- ]
3463
- }
3464
- },
3465
- "sdkName": "getSendPermissions",
3466
- "summary": "List send-permission rules",
3467
- "tag": "Sending",
3468
- "tagCommand": "sending"
3469
- },
3470
- {
3471
- "binaryResponse": false,
3472
- "bodyRequired": false,
3473
- "command": "get-sent-email",
3474
- "description": "Returns the full sent-email record by id, including\n`body_text` and `body_html` (omitted from the listing\nendpoint to keep paginated responses small). Use this when\ndiagnosing a specific send, e.g. inspecting the receiver's\nSMTP response on a `bounced` row or pulling the gate\ndenial detail on a `gate_denied` row.\n",
3475
- "hasJsonBody": false,
3476
- "method": "GET",
3477
- "operationId": "getSentEmail",
3478
- "path": "/sent-emails/{id}",
3479
- "pathParams": [
3480
- {
3481
- "description": "Resource UUID",
3482
- "enum": null,
3483
- "name": "id",
3484
- "required": true,
3485
- "type": "string"
3486
- }
3487
- ],
3488
- "queryParams": [],
3489
- "requestSchema": null,
3490
- "responseSchema": {
3491
- "description": "Full sent-email record, including `body_text` and\n`body_html`. Returned by /sent-emails/{id}.\n",
3492
- "allOf": [
3493
- {
3494
- "type": "object",
3495
- "description": "List-row projection of a sent-email record. Drops\n`body_text` and `body_html` to keep paginated responses\nsmall; fetch /sent-emails/{id} for the full record with\nbodies.\n",
3496
- "properties": {
3497
- "id": {
3498
- "type": "string",
3499
- "format": "uuid"
3500
- },
3501
- "status": {
3502
- "type": "string",
3503
- "description": "Lifecycle status of a sent_emails row. Possible values:\n\n - `queued`: pre-call INSERT; the outbound agent has not\n yet replied.\n - `submitted_to_agent`: agent accepted; `queue_id` is set.\n - `agent_failed`: agent rejected; `error_code` and\n `error_message` carry the reason.\n - `gate_denied`: a recipient-scope gate denied the send;\n the agent was never called. The `gates` array carries\n the denial detail. /send-mail returns 403 in this case\n so callers see the denial synchronously; /sent-emails\n additionally records the row for historical lookup,\n which is when this status appears in a listing.\n - `unknown`: terminal indeterminate; the on-box log\n poller couldn't classify the receiver's response.\n - `delivered` / `bounced` / `deferred` / `wait_timeout`:\n terminal delivery outcomes (see DeliveryStatus).\n",
3504
- "enum": [
3505
- "queued",
3506
- "submitted_to_agent",
3507
- "agent_failed",
3508
- "gate_denied",
3509
- "unknown",
3510
- "delivered",
3511
- "bounced",
3512
- "deferred",
3513
- "wait_timeout"
3514
- ]
3515
- },
3516
- "status_changed_at": {
3517
- "type": "string",
3518
- "format": "date-time",
3519
- "description": "Timestamp of the most recent status transition.\nPolling clients should treat `status='queued'` AND\n`status_changed_at` older than 5 minutes as\n\"stuck-queued\" (the post-tx UPDATE failed and the\nactual delivery state is recoverable from on-box logs\nvia `queue_id` when populated, or `request_id`).\n"
3520
- },
3521
- "created_at": {
3522
- "type": "string",
3523
- "format": "date-time"
3524
- },
3525
- "updated_at": {
3526
- "type": "string",
3527
- "format": "date-time"
3528
- },
3529
- "client_idempotency_key": {
3530
- "type": [
3531
- "string",
3532
- "null"
3533
- ],
3534
- "description": "Effective idempotency key used for this send. If the\ncaller passed the `Idempotency-Key` header, this is\nthat value; otherwise it's a server-derived hash of\nthe canonical request payload.\n"
3535
- },
3536
- "content_hash": {
3537
- "type": "string",
3538
- "description": "Stable hash of the canonical send payload."
3539
- },
3540
- "from_header": {
3541
- "type": "string",
3542
- "description": "Raw `From:` header as sent on the wire, including any\ndisplay name (e.g. `\"Acme Support\" <agent@acme.test>`).\n"
3543
- },
3544
- "from_address": {
3545
- "type": "string",
3546
- "description": "Bare email address parsed from `from_header`."
3547
- },
3548
- "to_header": {
3549
- "type": "string",
3550
- "description": "Raw `To:` header as sent on the wire, including any\ndisplay name.\n"
3551
- },
3552
- "to_address": {
3553
- "type": "string",
3554
- "description": "Bare email address parsed from `to_header`."
3555
- },
3556
- "subject": {
3557
- "type": "string"
3558
- },
3559
- "body_size_bytes": {
3560
- "type": "integer",
3561
- "description": "Total UTF-8 byte length of `body_text` + `body_html`.\nSurfaced on the list endpoint so callers can see \"this\nrow has a 4MB body\" without fetching it.\n"
3562
- },
3563
- "content_discarded_at": {
3564
- "type": [
3565
- "string",
3566
- "null"
3567
- ],
3568
- "format": "date-time",
3569
- "description": "Timestamp at which the bodies were discarded by an\nentitlement-driven retention policy. Null when bodies\nare still present. The detail endpoint returns\nnull-valued `body_text`/`body_html` for discarded rows.\n"
3570
- },
3571
- "message_id": {
3572
- "type": [
3573
- "string",
3574
- "null"
3575
- ],
3576
- "description": "Wire-level Message-ID assigned to the outbound message\n(RFC 5322). Null on rows that never reached signing\n(queued, gate_denied, agent_failed before signing).\n"
3577
- },
3578
- "in_reply_to": {
3579
- "type": [
3580
- "string",
3581
- "null"
3582
- ],
3583
- "description": "Wire-level In-Reply-To header value, when this send\nwas a reply.\n"
3584
- },
3585
- "email_references": {
3586
- "type": [
3587
- "string",
3588
- "null"
3589
- ],
3590
- "description": "Wire-level References header value, when this send\nwas a reply.\n"
3591
- },
3592
- "in_reply_to_email_id": {
3593
- "type": [
3594
- "string",
3595
- "null"
3596
- ],
3597
- "format": "uuid",
3598
- "description": "Reference to the inbound `emails.id` that this send\nreplied to, when known. Populated when the caller used\n/emails/{id}/reply or when /send-mail's `in_reply_to`\nmatched a stored inbound message_id in the same org.\n"
3599
- },
3600
- "queue_id": {
3601
- "type": [
3602
- "string",
3603
- "null"
3604
- ],
3605
- "description": "Message identifier assigned by Primitive's outbound\nrelay once the agent accepts the message. Null on\nqueued, gate_denied, and agent_failed rows.\n"
3606
- },
3607
- "smtp_response_code": {
3608
- "type": [
3609
- "integer",
3610
- "null"
3611
- ],
3612
- "description": "Receiver's 3-digit SMTP code (e.g. 250, 550, 451).\nPopulated on terminal delivery statuses; may be null\non a deferred where the agent never got an SMTP-level\nresponse (TCP refused, DNS failed, TLS handshake\nfailed). `smtp_response_text` still carries Postfix's\ndescriptive text in those cases.\n"
3613
- },
3614
- "smtp_response_text": {
3615
- "type": [
3616
- "string",
3617
- "null"
3618
- ],
3619
- "description": "Free-form text portion of the receiver's SMTP\nresponse. The most useful debugging signal on a\n`bounced` or `deferred` row.\n"
3620
- },
3621
- "smtp_enhanced_status_code": {
3622
- "type": [
3623
- "string",
3624
- "null"
3625
- ],
3626
- "description": "RFC 3463 enhanced status code (e.g. `5.1.1` for \"Bad\ndestination mailbox address\"). Distinct from\n`smtp_response_code`: the basic 3-digit code is coarse\n(550 = \"permanent failure\"), the enhanced code is\nfiner-grained.\n"
3627
- },
3628
- "dkim_selector": {
3629
- "type": [
3630
- "string",
3631
- "null"
3632
- ],
3633
- "description": "DKIM selector used to sign the outbound message.\nPublic DNS data; useful for diagnosing why a downstream\nverifier rejected the signature.\n"
3634
- },
3635
- "dkim_domain": {
3636
- "type": [
3637
- "string",
3638
- "null"
3639
- ],
3640
- "description": "DKIM signing domain."
3641
- },
3642
- "error_code": {
3643
- "type": [
3644
- "string",
3645
- "null"
3646
- ],
3647
- "description": "Stable public error code on `agent_failed` rows. The\nagent's internal codes are remapped to a stable public\ntaxonomy (see `publicAgentError` in the server) so this\nfield is safe to branch on across agent versions.\n"
3648
- },
3649
- "error_message": {
3650
- "type": [
3651
- "string",
3652
- "null"
3653
- ],
3654
- "description": "Free-form error message accompanying `error_code`."
3655
- },
3656
- "gates": {
3657
- "type": [
3658
- "array",
3659
- "null"
3660
- ],
3661
- "items": {
3662
- "type": "object",
3663
- "properties": {
3664
- "name": {
3665
- "type": "string",
3666
- "enum": [
3667
- "send_to_confirmed_domains",
3668
- "send_to_known_addresses"
3669
- ],
3670
- "description": "Public recipient-scope gate name that denied the send."
3671
- },
3672
- "reason": {
3673
- "type": "string",
3674
- "enum": [
3675
- "domain_not_confirmed",
3676
- "recipient_unauthenticated",
3677
- "recipient_not_known"
3678
- ],
3679
- "description": "Stable machine-readable denial reason."
3680
- },
3681
- "message": {
3682
- "type": "string",
3683
- "description": "Human-readable explanation of the gate denial."
3684
- },
3685
- "subject": {
3686
- "type": "string",
3687
- "description": "Domain or address the gate evaluated."
3688
- },
3689
- "fix": {
3690
- "type": "object",
3691
- "properties": {
3692
- "action": {
3693
- "type": "string",
3694
- "enum": [
3695
- "confirm_domain",
3696
- "sender_must_fix_authentication",
3697
- "wait_for_inbound"
3698
- ],
3699
- "description": "Suggested next action for the caller."
3700
- },
3701
- "subject": {
3702
- "type": "string",
3703
- "description": "Entity the action applies to."
3704
- }
3705
- },
3706
- "required": [
3707
- "action",
3708
- "subject"
3709
- ]
3710
- },
3711
- "docs_url": {
3712
- "type": "string",
3713
- "description": "Public docs URL with more context."
3714
- }
3715
- },
3716
- "required": [
3717
- "name",
3718
- "reason",
3719
- "message",
3720
- "subject"
3721
- ]
3722
- },
3723
- "description": "Gate-denial detail on `gate_denied` rows. Mirrors the\nsynchronous /send-mail 403 contract so a caller's\nGateDenial handler is the same across live denies and\nhistorical lookups. Null on every other status.\n"
3724
- },
3725
- "request_id": {
3726
- "type": [
3727
- "string",
3728
- "null"
3729
- ],
3730
- "description": "Server-issued request identifier from the original\n/send-mail call. Surfaced as the `X-Request-Id`\nresponse header on the live send and recorded here\nfor support escalation.\n"
3731
- }
3732
- },
3733
- "required": [
3734
- "id",
3735
- "status",
3736
- "status_changed_at",
3737
- "created_at",
3738
- "updated_at",
3739
- "content_hash",
3740
- "from_header",
3741
- "from_address",
3742
- "to_header",
3743
- "to_address",
3744
- "subject",
3745
- "body_size_bytes"
3746
- ]
3747
- },
3748
- {
3749
- "type": "object",
3750
- "properties": {
3751
- "body_text": {
3752
- "type": [
3753
- "string",
3754
- "null"
3755
- ],
3756
- "description": "Plain-text body sent on the wire. Null when the\nsend carried only an HTML body, or when bodies have\nbeen discarded post-send (`content_discarded_at`\nset).\n"
3757
- },
3758
- "body_html": {
3759
- "type": [
3760
- "string",
3761
- "null"
3762
- ],
3763
- "description": "HTML body sent on the wire. Null when the send\ncarried only a plain-text body, or when bodies\nhave been discarded post-send.\n"
3764
- }
3765
- }
3766
- }
3767
- ]
3768
- },
3769
- "sdkName": "getSentEmail",
3770
- "summary": "Get a sent email by id",
3771
- "tag": "Sending",
3772
- "tagCommand": "sending"
3773
- },
3774
- {
3775
- "binaryResponse": false,
3776
- "bodyRequired": false,
3777
- "command": "list-sent-emails",
3778
- "description": "Returns a paginated list of OUTBOUND emails the caller's\norg has sent via /send-mail (and /emails/{id}/reply, which\nforwards through /send-mail). Includes every recorded\nattempt, including gate-denied attempts that the agent\nnever called and rows still in `queued` state.\n\nFor inbound mail received at your verified domains, see\n/emails. There is no unified send/receive history endpoint;\nthe two surfaces are intentionally separate because the\nunderlying tables, statuses, and lifecycle differ.\n\nEmail bodies (`body_text`, `body_html`) are NOT included on\nlist rows so a 50-row page can't balloon into a multi-MB\nresponse when sends are near the 5MB body cap. Use\n/sent-emails/{id} to fetch a single row with bodies, or\ncross-reference by `client_idempotency_key` if the caller\nalready has the body locally.\n",
3779
- "hasJsonBody": false,
3780
- "method": "GET",
3781
- "operationId": "listSentEmails",
3782
- "path": "/sent-emails",
3783
- "pathParams": [],
3784
- "queryParams": [
3785
- {
3786
- "description": "Pagination cursor from a previous response's `meta.cursor` field.\nFormat: `{ISO-datetime}|{id}`\n",
3787
- "enum": null,
3788
- "name": "cursor",
3789
- "required": false,
3790
- "type": "string"
3791
- },
3792
- {
3793
- "description": "Number of results per page",
3794
- "enum": null,
3795
- "name": "limit",
3796
- "required": false,
3797
- "type": "integer"
3798
- },
3799
- {
3800
- "description": "Filter to rows in this status. Useful for polling\nqueued rows that haven't transitioned, auditing\ngate-denied attempts, or listing only successful\ndeliveries.\n",
3801
- "enum": null,
3802
- "name": "status",
3803
- "required": false,
3804
- "type": "string"
3805
- },
3806
- {
3807
- "description": "Filter to the row matching a specific server-issued\n`request_id`. The /send-mail response surfaces\n`request_id` on every send; this lookup lets the\ncaller find the historical row for a given live call\nwithout remembering its `id`.\n",
3808
- "enum": null,
3809
- "name": "request_id",
3810
- "required": false,
3811
- "type": "string"
3812
- },
3813
- {
3814
- "description": "Filter to rows with the given `client_idempotency_key`.\nMultiple rows can share a key (a retry that hit the\nidempotent-replay path returns the same row, but a\nretry with a DIFFERENT canonical payload under the\nsame key is rejected by /send-mail before the row is\nwritten, so duplicates are bounded).\n",
3815
- "enum": null,
3816
- "name": "idempotency_key",
3817
- "required": false,
3818
- "type": "string"
3819
- },
3820
- {
3821
- "description": "Inclusive lower bound on `created_at`.",
3822
- "enum": null,
3823
- "name": "date_from",
3824
- "required": false,
3825
- "type": "string"
3826
- },
3827
- {
3828
- "description": "Inclusive upper bound on `created_at`.",
3829
- "enum": null,
3830
- "name": "date_to",
3831
- "required": false,
3832
- "type": "string"
3833
- }
3834
- ],
3835
- "requestSchema": null,
3836
- "responseSchema": {
3837
- "type": "array",
3838
- "items": {
3839
- "type": "object",
3840
- "description": "List-row projection of a sent-email record. Drops\n`body_text` and `body_html` to keep paginated responses\nsmall; fetch /sent-emails/{id} for the full record with\nbodies.\n",
3841
- "properties": {
3842
- "id": {
3843
- "type": "string",
3844
- "format": "uuid"
3845
- },
3846
- "status": {
3847
- "type": "string",
3848
- "description": "Lifecycle status of a sent_emails row. Possible values:\n\n - `queued`: pre-call INSERT; the outbound agent has not\n yet replied.\n - `submitted_to_agent`: agent accepted; `queue_id` is set.\n - `agent_failed`: agent rejected; `error_code` and\n `error_message` carry the reason.\n - `gate_denied`: a recipient-scope gate denied the send;\n the agent was never called. The `gates` array carries\n the denial detail. /send-mail returns 403 in this case\n so callers see the denial synchronously; /sent-emails\n additionally records the row for historical lookup,\n which is when this status appears in a listing.\n - `unknown`: terminal indeterminate; the on-box log\n poller couldn't classify the receiver's response.\n - `delivered` / `bounced` / `deferred` / `wait_timeout`:\n terminal delivery outcomes (see DeliveryStatus).\n",
3849
- "enum": [
3850
- "queued",
3851
- "submitted_to_agent",
3852
- "agent_failed",
3853
- "gate_denied",
3854
- "unknown",
3855
- "delivered",
3856
- "bounced",
3857
- "deferred",
3858
- "wait_timeout"
3859
- ]
3860
- },
3861
- "status_changed_at": {
3862
- "type": "string",
3863
- "format": "date-time",
3864
- "description": "Timestamp of the most recent status transition.\nPolling clients should treat `status='queued'` AND\n`status_changed_at` older than 5 minutes as\n\"stuck-queued\" (the post-tx UPDATE failed and the\nactual delivery state is recoverable from on-box logs\nvia `queue_id` when populated, or `request_id`).\n"
3865
- },
3866
- "created_at": {
3867
- "type": "string",
3868
- "format": "date-time"
3869
- },
3870
- "updated_at": {
3871
- "type": "string",
3872
- "format": "date-time"
3873
- },
3874
- "client_idempotency_key": {
3875
- "type": [
3876
- "string",
3877
- "null"
3878
- ],
3879
- "description": "Effective idempotency key used for this send. If the\ncaller passed the `Idempotency-Key` header, this is\nthat value; otherwise it's a server-derived hash of\nthe canonical request payload.\n"
3880
- },
3881
- "content_hash": {
3882
- "type": "string",
3883
- "description": "Stable hash of the canonical send payload."
3884
- },
3885
- "from_header": {
3886
- "type": "string",
3887
- "description": "Raw `From:` header as sent on the wire, including any\ndisplay name (e.g. `\"Acme Support\" <agent@acme.test>`).\n"
3888
- },
3889
- "from_address": {
3890
- "type": "string",
3891
- "description": "Bare email address parsed from `from_header`."
3892
- },
3893
- "to_header": {
3894
- "type": "string",
3895
- "description": "Raw `To:` header as sent on the wire, including any\ndisplay name.\n"
3896
- },
3897
- "to_address": {
3898
- "type": "string",
3899
- "description": "Bare email address parsed from `to_header`."
3900
- },
3901
- "subject": {
3902
- "type": "string"
3903
- },
3904
- "body_size_bytes": {
3905
- "type": "integer",
3906
- "description": "Total UTF-8 byte length of `body_text` + `body_html`.\nSurfaced on the list endpoint so callers can see \"this\nrow has a 4MB body\" without fetching it.\n"
3907
- },
3908
- "content_discarded_at": {
3909
- "type": [
3910
- "string",
3911
- "null"
3912
- ],
3913
- "format": "date-time",
3914
- "description": "Timestamp at which the bodies were discarded by an\nentitlement-driven retention policy. Null when bodies\nare still present. The detail endpoint returns\nnull-valued `body_text`/`body_html` for discarded rows.\n"
3915
- },
3916
- "message_id": {
3917
- "type": [
3918
- "string",
3919
- "null"
3920
- ],
3921
- "description": "Wire-level Message-ID assigned to the outbound message\n(RFC 5322). Null on rows that never reached signing\n(queued, gate_denied, agent_failed before signing).\n"
3922
- },
3923
- "in_reply_to": {
3924
- "type": [
3925
- "string",
3926
- "null"
3927
- ],
3928
- "description": "Wire-level In-Reply-To header value, when this send\nwas a reply.\n"
3929
- },
3930
- "email_references": {
3931
- "type": [
3932
- "string",
3933
- "null"
3934
- ],
3935
- "description": "Wire-level References header value, when this send\nwas a reply.\n"
3936
- },
3937
- "in_reply_to_email_id": {
3938
- "type": [
3939
- "string",
3940
- "null"
3941
- ],
3942
- "format": "uuid",
3943
- "description": "Reference to the inbound `emails.id` that this send\nreplied to, when known. Populated when the caller used\n/emails/{id}/reply or when /send-mail's `in_reply_to`\nmatched a stored inbound message_id in the same org.\n"
3944
- },
3945
- "queue_id": {
3946
- "type": [
3947
- "string",
3948
- "null"
3949
- ],
3950
- "description": "Message identifier assigned by Primitive's outbound\nrelay once the agent accepts the message. Null on\nqueued, gate_denied, and agent_failed rows.\n"
3951
- },
3952
- "smtp_response_code": {
3953
- "type": [
3954
- "integer",
3955
- "null"
3956
- ],
3957
- "description": "Receiver's 3-digit SMTP code (e.g. 250, 550, 451).\nPopulated on terminal delivery statuses; may be null\non a deferred where the agent never got an SMTP-level\nresponse (TCP refused, DNS failed, TLS handshake\nfailed). `smtp_response_text` still carries Postfix's\ndescriptive text in those cases.\n"
3958
- },
3959
- "smtp_response_text": {
3960
- "type": [
3961
- "string",
3962
- "null"
3963
- ],
3964
- "description": "Free-form text portion of the receiver's SMTP\nresponse. The most useful debugging signal on a\n`bounced` or `deferred` row.\n"
3965
- },
3966
- "smtp_enhanced_status_code": {
3967
- "type": [
3968
- "string",
3969
- "null"
3970
- ],
3971
- "description": "RFC 3463 enhanced status code (e.g. `5.1.1` for \"Bad\ndestination mailbox address\"). Distinct from\n`smtp_response_code`: the basic 3-digit code is coarse\n(550 = \"permanent failure\"), the enhanced code is\nfiner-grained.\n"
3972
- },
3973
- "dkim_selector": {
3974
- "type": [
3975
- "string",
3976
- "null"
3977
- ],
3978
- "description": "DKIM selector used to sign the outbound message.\nPublic DNS data; useful for diagnosing why a downstream\nverifier rejected the signature.\n"
3979
- },
3980
- "dkim_domain": {
3981
- "type": [
3982
- "string",
3983
- "null"
3984
- ],
3985
- "description": "DKIM signing domain."
3986
- },
3987
- "error_code": {
3988
- "type": [
3989
- "string",
3990
- "null"
3991
- ],
3992
- "description": "Stable public error code on `agent_failed` rows. The\nagent's internal codes are remapped to a stable public\ntaxonomy (see `publicAgentError` in the server) so this\nfield is safe to branch on across agent versions.\n"
3993
- },
3994
- "error_message": {
3995
- "type": [
3996
- "string",
3997
- "null"
3998
- ],
3999
- "description": "Free-form error message accompanying `error_code`."
4000
- },
4001
- "gates": {
4002
- "type": [
4003
- "array",
4004
- "null"
4005
- ],
4006
- "items": {
4007
- "type": "object",
4008
- "properties": {
4009
- "name": {
4010
- "type": "string",
4011
- "enum": [
4012
- "send_to_confirmed_domains",
4013
- "send_to_known_addresses"
4014
- ],
4015
- "description": "Public recipient-scope gate name that denied the send."
4016
- },
4017
- "reason": {
4018
- "type": "string",
4019
- "enum": [
4020
- "domain_not_confirmed",
4021
- "recipient_unauthenticated",
4022
- "recipient_not_known"
4023
- ],
4024
- "description": "Stable machine-readable denial reason."
4025
- },
4026
- "message": {
4027
- "type": "string",
4028
- "description": "Human-readable explanation of the gate denial."
4029
- },
4030
- "subject": {
4031
- "type": "string",
4032
- "description": "Domain or address the gate evaluated."
4033
- },
4034
- "fix": {
4035
- "type": "object",
4036
- "properties": {
4037
- "action": {
4038
- "type": "string",
4039
- "enum": [
4040
- "confirm_domain",
4041
- "sender_must_fix_authentication",
4042
- "wait_for_inbound"
4043
- ],
4044
- "description": "Suggested next action for the caller."
4045
- },
4046
- "subject": {
4047
- "type": "string",
4048
- "description": "Entity the action applies to."
4049
- }
4050
- },
4051
- "required": [
4052
- "action",
4053
- "subject"
4054
- ]
4055
- },
4056
- "docs_url": {
4057
- "type": "string",
4058
- "description": "Public docs URL with more context."
4059
- }
4060
- },
4061
- "required": [
4062
- "name",
4063
- "reason",
4064
- "message",
4065
- "subject"
4066
- ]
4067
- },
4068
- "description": "Gate-denial detail on `gate_denied` rows. Mirrors the\nsynchronous /send-mail 403 contract so a caller's\nGateDenial handler is the same across live denies and\nhistorical lookups. Null on every other status.\n"
4069
- },
4070
- "request_id": {
4071
- "type": [
4072
- "string",
4073
- "null"
4074
- ],
4075
- "description": "Server-issued request identifier from the original\n/send-mail call. Surfaced as the `X-Request-Id`\nresponse header on the live send and recorded here\nfor support escalation.\n"
4076
- }
4077
- },
4078
- "required": [
4079
- "id",
4080
- "status",
4081
- "status_changed_at",
4082
- "created_at",
4083
- "updated_at",
4084
- "content_hash",
4085
- "from_header",
4086
- "from_address",
4087
- "to_header",
4088
- "to_address",
4089
- "subject",
4090
- "body_size_bytes"
4091
- ]
4092
- }
4093
- },
4094
- "sdkName": "listSentEmails",
4095
- "summary": "List outbound sent emails",
4096
- "tag": "Sending",
4097
- "tagCommand": "sending"
4098
- },
4099
- {
4100
- "binaryResponse": false,
4101
- "bodyRequired": true,
4102
- "command": "reply-to-email",
4103
- "description": "Sends an outbound reply to the inbound email identified by `id`.\nThreading headers (`In-Reply-To`, `References`), recipient\nderivation (Reply-To, then From, then bare sender), and the\n`Re:` subject prefix are all derived server-side from the\nstored inbound row. The request body carries only the message\nbody and optional `wait` flag; passing any header or recipient\noverride is rejected by the schema (`additionalProperties:\nfalse`).\n\nForwards through the same gates as `/send-mail`: the response\nstatus, error envelope, and `idempotent_replay` flag mirror\nthe send-mail contract verbatim.\n",
4104
- "hasJsonBody": true,
4105
- "method": "POST",
4106
- "operationId": "replyToEmail",
4107
- "path": "/emails/{id}/reply",
4108
- "pathParams": [
4109
- {
4110
- "description": "Resource UUID",
4111
- "enum": null,
4112
- "name": "id",
4113
- "required": true,
4114
- "type": "string"
4115
- }
4116
- ],
4117
- "queryParams": [],
4118
- "requestSchema": {
4119
- "type": "object",
4120
- "additionalProperties": false,
4121
- "description": "Body shape for `/emails/{id}/reply`. Intentionally narrow:\nrecipients (`to`), subject, and threading headers\n(`in_reply_to`, `references`) are derived server-side from\nthe inbound row referenced by the path id and are rejected by\n`additionalProperties` if passed (returns 400).\n\n`from` IS allowed because of legitimate use cases (display-name\naddition, replying from a different verified outbound address,\nmulti-team triage). Send-mail's per-send `canSendFrom` gate\nvalidates the from-domain regardless, so the override carries\nno extra privilege.\n",
4122
- "properties": {
4123
- "body_text": {
4124
- "type": "string",
4125
- "description": "Plain-text reply body. At least one of body_text or body_html is required. The combined UTF-8 byte length of body_text and body_html must be at most 262144 bytes (same cap as send-mail)."
4126
- },
4127
- "body_html": {
4128
- "type": "string",
4129
- "description": "HTML reply body. At least one of body_text or body_html is required."
4130
- },
4131
- "from": {
4132
- "type": "string",
4133
- "minLength": 3,
4134
- "maxLength": 998,
4135
- "description": "Optional override for the reply's From header. Defaults to\nthe inbound's recipient. Use to add a display name (`\"Acme\nSupport\" <agent@company.com>`) or to reply from a different\nverified outbound address (e.g. multi-team routing where\nsupport@ triages to billing@). The from-domain must be a\nverified outbound domain for your org, same as send-mail.\n"
4136
- },
4137
- "wait": {
4138
- "type": "boolean",
4139
- "description": "When true, wait for the first downstream SMTP delivery outcome before returning, mirroring the send-mail `wait` semantics."
4140
- }
4141
- }
4142
- },
4143
- "responseSchema": {
4144
- "type": "object",
4145
- "properties": {
4146
- "id": {
4147
- "type": "string",
4148
- "description": "Persisted sent-email attempt ID."
4149
- },
4150
- "status": {
4151
- "type": "string",
4152
- "description": "Lifecycle status of a sent_emails row. Possible values:\n\n - `queued`: pre-call INSERT; the outbound agent has not\n yet replied.\n - `submitted_to_agent`: agent accepted; `queue_id` is set.\n - `agent_failed`: agent rejected; `error_code` and\n `error_message` carry the reason.\n - `gate_denied`: a recipient-scope gate denied the send;\n the agent was never called. The `gates` array carries\n the denial detail. /send-mail returns 403 in this case\n so callers see the denial synchronously; /sent-emails\n additionally records the row for historical lookup,\n which is when this status appears in a listing.\n - `unknown`: terminal indeterminate; the on-box log\n poller couldn't classify the receiver's response.\n - `delivered` / `bounced` / `deferred` / `wait_timeout`:\n terminal delivery outcomes (see DeliveryStatus).\n",
4153
- "enum": [
4154
- "queued",
4155
- "submitted_to_agent",
4156
- "agent_failed",
4157
- "gate_denied",
4158
- "unknown",
4159
- "delivered",
4160
- "bounced",
4161
- "deferred",
4162
- "wait_timeout"
4163
- ]
4164
- },
4165
- "from": {
4166
- "type": "string",
4167
- "description": "Bare from-address actually written on the wire. Echoed\non every success branch so callers can confirm what\nwent out, particularly useful for the /emails/{id}/reply\npath where `from` is server-derived from the inbound's\nrecipient when the caller doesn't override.\n\nFor sends where the caller passed a from-header that\nincluded a display name (e.g. `\"Acme Support\" <support@acme.test>`),\nthis field is the parsed bare address (`support@acme.test`).\nThe display name was sent on the wire intact; this field\njust makes the address easy to compare against allowlists.\n"
4168
- },
4169
- "queue_id": {
4170
- "type": [
4171
- "string",
4172
- "null"
4173
- ],
4174
- "description": "Message identifier assigned by Primitive's OUTBOUND relay\n(the box that signs your mail and submits it to the\nreceiving MTA). NOT the receiver's queue id.\n\nThe receiver may also report its own queue id in\n`smtp_response_text` (e.g. `\"250 2.0.0 Ok: queued as\n99D111927CDA\"` from a Postfix receiver). Those two ids\nrefer to different mail systems and are NOT comparable.\nTreat `queue_id` as Primitive-internal and the\nreceiver's id as remote-system-internal.\n\nNull on rows that never reached the relay (queued,\ngate_denied, agent_failed before signing).\n"
4175
- },
4176
- "accepted": {
4177
- "type": "array",
4178
- "items": {
4179
- "type": "string"
4180
- },
4181
- "description": "Recipient addresses accepted by the relay."
4182
- },
4183
- "rejected": {
4184
- "type": "array",
4185
- "items": {
4186
- "type": "string"
4187
- },
4188
- "description": "Recipient addresses rejected by the relay."
4189
- },
4190
- "client_idempotency_key": {
4191
- "type": "string",
4192
- "description": "Effective idempotency key used for this send."
4193
- },
4194
- "request_id": {
4195
- "type": "string",
4196
- "description": "Server-issued request identifier for support and tracing."
4197
- },
4198
- "content_hash": {
4199
- "type": "string",
4200
- "description": "Stable hash of the canonical send payload."
4201
- },
4202
- "delivery_status": {
4203
- "type": "string",
4204
- "description": "Narrower enum covering only the four terminal delivery\noutcomes returned to a synchronous `wait: true` send.\n\nOn the SendMailResult shape, `delivery_status` is always\nequal to `status` whenever both are present (i.e. on\nterminal-state replays and live wait=true responses).\nThe two fields exist so callers that want to type-narrow\non \"this is a delivery outcome\" can pattern-match against\nthe four-value enum without handling the broader\nSentEmailStatus value set (which also covers `queued`,\n`submitted_to_agent`, `agent_failed`, `gate_denied`,\n`unknown`).\n\nOn async-mode and pre-terminal responses, `delivery_status`\nis absent and only `status` is populated. Use `status` if\nyou want a single field that's always present.\n",
4205
- "enum": [
4206
- "delivered",
4207
- "bounced",
4208
- "deferred",
4209
- "wait_timeout"
4210
- ]
4211
- },
4212
- "smtp_response_code": {
4213
- "type": [
4214
- "integer",
4215
- "null"
4216
- ],
4217
- "description": "SMTP response code from the first downstream delivery outcome when wait is true."
4218
- },
4219
- "smtp_response_text": {
4220
- "type": "string",
4221
- "description": "SMTP response text from the first downstream delivery outcome when wait is true."
4222
- },
4223
- "idempotent_replay": {
4224
- "type": "boolean",
4225
- "description": "True when the response replays a previously-recorded send\nkeyed by `client_idempotency_key` (same key, same canonical\npayload). False on a fresh send and on gate-denied\nresponses. Lets callers branch on cache state without\ndiffing fields.\n"
4226
- }
4227
- },
4228
- "required": [
4229
- "id",
4230
- "status",
4231
- "from",
4232
- "queue_id",
4233
- "accepted",
4234
- "rejected",
4235
- "client_idempotency_key",
4236
- "request_id",
4237
- "content_hash",
4238
- "idempotent_replay"
4239
- ]
4240
- },
4241
- "sdkName": "replyToEmail",
4242
- "summary": "Reply to an inbound email",
4243
- "tag": "Sending",
4244
- "tagCommand": "sending"
4245
- },
4246
- {
4247
- "binaryResponse": false,
4248
- "bodyRequired": true,
4249
- "command": "send-email",
4250
- "description": "Sends an outbound email through Primitive's outbound relay. By default\nthe request returns once the relay accepts the message for delivery.\nSet `wait: true` to wait for the first downstream SMTP delivery outcome.\n\n**Host routing.** /send-mail is served by the attachments-\nsupporting host (`https://api.primitive.dev/v1`) so the\nrequest body can carry inline attachments up to ~30 MiB raw.\nThe primary host (`https://www.primitive.dev/api/v1`) also\naccepts /send-mail for attachment-free sends; sends WITH\nattachments to the primary host return 413\n`attachments_unsupported_on_this_endpoint`. The typed SDKs\nroute /send-mail to the attachments host automatically.\n",
4251
- "hasJsonBody": true,
4252
- "method": "POST",
4253
- "operationId": "sendEmail",
4254
- "path": "/send-mail",
4255
- "pathParams": [],
4256
- "queryParams": [],
4257
- "requestSchema": {
4258
- "type": "object",
4259
- "additionalProperties": false,
4260
- "properties": {
4261
- "from": {
4262
- "type": "string",
4263
- "minLength": 3,
4264
- "maxLength": 998,
4265
- "description": "RFC 5322 From header. The sender domain must be a verified outbound domain for your organization."
4266
- },
4267
- "to": {
4268
- "type": "string",
4269
- "minLength": 3,
4270
- "maxLength": 320,
4271
- "description": "Recipient address. Recipient eligibility depends on your account's outbound entitlements."
4272
- },
4273
- "subject": {
4274
- "type": "string",
4275
- "minLength": 1,
4276
- "maxLength": 998,
4277
- "description": "Subject line for the outbound message"
4278
- },
4279
- "body_text": {
4280
- "type": "string",
4281
- "description": "Plain-text message body. At least one of body_text or body_html is required. The combined UTF-8 byte length of body_text and body_html must be at most 262144 bytes."
4282
- },
4283
- "body_html": {
4284
- "type": "string",
4285
- "description": "HTML message body. At least one of body_text or body_html is required. The combined UTF-8 byte length of body_text and body_html must be at most 262144 bytes."
4286
- },
4287
- "in_reply_to": {
4288
- "type": "string",
4289
- "minLength": 1,
4290
- "maxLength": 998,
4291
- "pattern": "^[^\\x00-\\x1F\\x7F]+$",
4292
- "description": "Message-ID of the direct parent email when sending a threaded reply."
4293
- },
4294
- "references": {
4295
- "type": "array",
4296
- "maxItems": 100,
4297
- "description": "Full ordered message-id chain for the thread.",
4298
- "items": {
4299
- "type": "string",
4300
- "minLength": 1,
4301
- "maxLength": 998,
4302
- "pattern": "^[^\\x00-\\x1F\\x7F]+$"
4303
- }
4304
- },
4305
- "wait": {
4306
- "type": "boolean",
4307
- "description": "When true, wait for the first downstream SMTP delivery outcome before returning."
4308
- },
4309
- "wait_timeout_ms": {
4310
- "type": "integer",
4311
- "minimum": 1000,
4312
- "maximum": 30000,
4313
- "description": "Maximum time to wait for a delivery outcome when wait is true. Defaults to 30000."
4314
- }
4315
- },
4316
- "required": [
4317
- "from",
4318
- "to",
4319
- "subject"
4320
- ]
4321
- },
4322
- "responseSchema": {
4323
- "type": "object",
4324
- "properties": {
4325
- "id": {
4326
- "type": "string",
4327
- "description": "Persisted sent-email attempt ID."
4328
- },
4329
- "status": {
4330
- "type": "string",
4331
- "description": "Lifecycle status of a sent_emails row. Possible values:\n\n - `queued`: pre-call INSERT; the outbound agent has not\n yet replied.\n - `submitted_to_agent`: agent accepted; `queue_id` is set.\n - `agent_failed`: agent rejected; `error_code` and\n `error_message` carry the reason.\n - `gate_denied`: a recipient-scope gate denied the send;\n the agent was never called. The `gates` array carries\n the denial detail. /send-mail returns 403 in this case\n so callers see the denial synchronously; /sent-emails\n additionally records the row for historical lookup,\n which is when this status appears in a listing.\n - `unknown`: terminal indeterminate; the on-box log\n poller couldn't classify the receiver's response.\n - `delivered` / `bounced` / `deferred` / `wait_timeout`:\n terminal delivery outcomes (see DeliveryStatus).\n",
4332
- "enum": [
4333
- "queued",
4334
- "submitted_to_agent",
4335
- "agent_failed",
4336
- "gate_denied",
4337
- "unknown",
4338
- "delivered",
4339
- "bounced",
4340
- "deferred",
4341
- "wait_timeout"
4342
- ]
4343
- },
4344
- "from": {
4345
- "type": "string",
4346
- "description": "Bare from-address actually written on the wire. Echoed\non every success branch so callers can confirm what\nwent out, particularly useful for the /emails/{id}/reply\npath where `from` is server-derived from the inbound's\nrecipient when the caller doesn't override.\n\nFor sends where the caller passed a from-header that\nincluded a display name (e.g. `\"Acme Support\" <support@acme.test>`),\nthis field is the parsed bare address (`support@acme.test`).\nThe display name was sent on the wire intact; this field\njust makes the address easy to compare against allowlists.\n"
4347
- },
4348
- "queue_id": {
4349
- "type": [
4350
- "string",
4351
- "null"
4352
- ],
4353
- "description": "Message identifier assigned by Primitive's OUTBOUND relay\n(the box that signs your mail and submits it to the\nreceiving MTA). NOT the receiver's queue id.\n\nThe receiver may also report its own queue id in\n`smtp_response_text` (e.g. `\"250 2.0.0 Ok: queued as\n99D111927CDA\"` from a Postfix receiver). Those two ids\nrefer to different mail systems and are NOT comparable.\nTreat `queue_id` as Primitive-internal and the\nreceiver's id as remote-system-internal.\n\nNull on rows that never reached the relay (queued,\ngate_denied, agent_failed before signing).\n"
4354
- },
4355
- "accepted": {
4356
- "type": "array",
4357
- "items": {
4358
- "type": "string"
4359
- },
4360
- "description": "Recipient addresses accepted by the relay."
4361
- },
4362
- "rejected": {
4363
- "type": "array",
4364
- "items": {
4365
- "type": "string"
4366
- },
4367
- "description": "Recipient addresses rejected by the relay."
4368
- },
4369
- "client_idempotency_key": {
4370
- "type": "string",
4371
- "description": "Effective idempotency key used for this send."
4372
- },
4373
- "request_id": {
4374
- "type": "string",
4375
- "description": "Server-issued request identifier for support and tracing."
4376
- },
4377
- "content_hash": {
4378
- "type": "string",
4379
- "description": "Stable hash of the canonical send payload."
4380
- },
4381
- "delivery_status": {
4382
- "type": "string",
4383
- "description": "Narrower enum covering only the four terminal delivery\noutcomes returned to a synchronous `wait: true` send.\n\nOn the SendMailResult shape, `delivery_status` is always\nequal to `status` whenever both are present (i.e. on\nterminal-state replays and live wait=true responses).\nThe two fields exist so callers that want to type-narrow\non \"this is a delivery outcome\" can pattern-match against\nthe four-value enum without handling the broader\nSentEmailStatus value set (which also covers `queued`,\n`submitted_to_agent`, `agent_failed`, `gate_denied`,\n`unknown`).\n\nOn async-mode and pre-terminal responses, `delivery_status`\nis absent and only `status` is populated. Use `status` if\nyou want a single field that's always present.\n",
4384
- "enum": [
4385
- "delivered",
4386
- "bounced",
4387
- "deferred",
4388
- "wait_timeout"
4389
- ]
4390
- },
4391
- "smtp_response_code": {
4392
- "type": [
4393
- "integer",
4394
- "null"
4395
- ],
4396
- "description": "SMTP response code from the first downstream delivery outcome when wait is true."
4397
- },
4398
- "smtp_response_text": {
4399
- "type": "string",
4400
- "description": "SMTP response text from the first downstream delivery outcome when wait is true."
4401
- },
4402
- "idempotent_replay": {
4403
- "type": "boolean",
4404
- "description": "True when the response replays a previously-recorded send\nkeyed by `client_idempotency_key` (same key, same canonical\npayload). False on a fresh send and on gate-denied\nresponses. Lets callers branch on cache state without\ndiffing fields.\n"
4405
- }
4406
- },
4407
- "required": [
4408
- "id",
4409
- "status",
4410
- "from",
4411
- "queue_id",
4412
- "accepted",
4413
- "rejected",
4414
- "client_idempotency_key",
4415
- "request_id",
4416
- "content_hash",
4417
- "idempotent_replay"
4418
- ]
4419
- },
4420
- "sdkName": "sendEmail",
4421
- "summary": "Send outbound email",
4422
- "tag": "Sending",
4423
- "tagCommand": "sending"
4424
- },
4425
- {
4426
- "binaryResponse": false,
4427
- "bodyRequired": false,
4428
- "command": "list-deliveries",
4429
- "description": "Returns a paginated list of webhook delivery attempts. Each delivery\nincludes a nested `email` object with sender, recipient, and subject.\n",
4430
- "hasJsonBody": false,
4431
- "method": "GET",
4432
- "operationId": "listDeliveries",
4433
- "path": "/webhooks/deliveries",
4434
- "pathParams": [],
4435
- "queryParams": [
4436
- {
4437
- "description": "Pagination cursor from a previous response's `meta.cursor` field.\nFormat: `{ISO-datetime}|{id}`\n",
4438
- "enum": null,
4439
- "name": "cursor",
4440
- "required": false,
4441
- "type": "string"
4442
- },
4443
- {
4444
- "description": "Number of results per page",
4445
- "enum": null,
4446
- "name": "limit",
4447
- "required": false,
4448
- "type": "integer"
4449
- },
4450
- {
4451
- "description": "Filter by email ID",
4452
- "enum": null,
4453
- "name": "email_id",
4454
- "required": false,
4455
- "type": "string"
4456
- },
4457
- {
4458
- "description": "Filter by delivery status",
4459
- "enum": [
4460
- "pending",
4461
- "delivered",
4462
- "header_confirmed",
4463
- "failed"
4464
- ],
4465
- "name": "status",
4466
- "required": false,
4467
- "type": "string"
4468
- },
4469
- {
4470
- "description": "Filter deliveries created on or after this timestamp",
4471
- "enum": null,
4472
- "name": "date_from",
4473
- "required": false,
4474
- "type": "string"
4475
- },
4476
- {
4477
- "description": "Filter deliveries created on or before this timestamp",
4478
- "enum": null,
4479
- "name": "date_to",
4480
- "required": false,
4481
- "type": "string"
4482
- }
4483
- ],
4484
- "requestSchema": null,
4485
- "responseSchema": {
4486
- "type": "array",
4487
- "items": {
4488
- "type": "object",
4489
- "properties": {
4490
- "id": {
4491
- "type": "string",
4492
- "description": "Delivery ID (numeric string)"
4493
- },
4494
- "email_id": {
4495
- "type": "string",
4496
- "format": "uuid"
4497
- },
4498
- "org_id": {
4499
- "type": "string",
4500
- "format": "uuid"
4501
- },
4502
- "endpoint_id": {
4503
- "type": "string",
4504
- "format": "uuid"
4505
- },
4506
- "endpoint_url": {
4507
- "type": "string"
4508
- },
4509
- "status": {
4510
- "type": "string",
4511
- "enum": [
4512
- "pending",
4513
- "delivered",
4514
- "header_confirmed",
4515
- "failed"
4516
- ]
4517
- },
4518
- "attempt_count": {
4519
- "type": "integer"
4520
- },
4521
- "duration_ms": {
4522
- "type": [
4523
- "integer",
4524
- "null"
4525
- ]
4526
- },
4527
- "last_error": {
4528
- "type": [
4529
- "string",
4530
- "null"
4531
- ]
4532
- },
4533
- "created_at": {
4534
- "type": "string",
4535
- "format": "date-time"
4536
- },
4537
- "updated_at": {
4538
- "type": "string",
4539
- "format": "date-time"
4540
- },
4541
- "email": {
4542
- "type": [
4543
- "object",
4544
- "null"
4545
- ],
4546
- "properties": {
4547
- "sender": {
4548
- "type": "string"
4549
- },
4550
- "recipient": {
4551
- "type": "string"
4552
- },
4553
- "subject": {
4554
- "type": [
4555
- "string",
4556
- "null"
4557
- ]
4558
- }
4559
- },
4560
- "required": [
4561
- "sender",
4562
- "recipient"
4563
- ]
4564
- }
4565
- },
4566
- "required": [
4567
- "id",
4568
- "email_id",
4569
- "org_id",
4570
- "endpoint_id",
4571
- "endpoint_url",
4572
- "status",
4573
- "attempt_count",
4574
- "created_at",
4575
- "updated_at"
4576
- ]
4577
- }
4578
- },
4579
- "sdkName": "listDeliveries",
4580
- "summary": "List webhook deliveries",
4581
- "tag": "Webhook Deliveries",
4582
- "tagCommand": "webhook-deliveries"
4583
- },
4584
- {
4585
- "binaryResponse": false,
4586
- "bodyRequired": false,
4587
- "command": "replay-delivery",
4588
- "description": "Re-sends the stored webhook payload from a previous delivery attempt.\nIf the original endpoint is still active, it is targeted. If the\noriginal endpoint was deleted, the oldest active endpoint is used.\nDeactivated endpoints cannot be replayed to. Rate limited per-org,\nsharing an org-wide budget with email replays.\n",
4589
- "hasJsonBody": false,
4590
- "method": "POST",
4591
- "operationId": "replayDelivery",
4592
- "path": "/webhooks/deliveries/{id}/replay",
4593
- "pathParams": [
4594
- {
4595
- "description": "Delivery ID (numeric)",
4596
- "enum": null,
4597
- "name": "id",
4598
- "required": true,
4599
- "type": "string"
4600
- }
4601
- ],
4602
- "queryParams": [],
4603
- "requestSchema": null,
4604
- "responseSchema": {
4605
- "type": "object",
4606
- "properties": {
4607
- "delivered": {
4608
- "type": "integer",
4609
- "description": "Number of successful deliveries"
4610
- },
4611
- "failed": {
4612
- "type": "integer",
4613
- "description": "Number of failed deliveries"
4614
- }
4615
- },
4616
- "required": [
4617
- "delivered",
4618
- "failed"
4619
- ]
4620
- },
4621
- "sdkName": "replayDelivery",
4622
- "summary": "Replay a webhook delivery",
4623
- "tag": "Webhook Deliveries",
4624
- "tagCommand": "webhook-deliveries"
4625
- }
4626
- ];