@wspc/cli 0.0.10 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/spec/openapi.json CHANGED
@@ -1282,6 +1282,151 @@
1282
1282
  "created_at"
1283
1283
  ]
1284
1284
  },
1285
+ "CreateDomainBody": {
1286
+ "type": "object",
1287
+ "properties": {
1288
+ "domain": {
1289
+ "type": "string",
1290
+ "description": "Custom domain hostname to register with the provider, for example `mail.example.com`."
1291
+ }
1292
+ },
1293
+ "required": [
1294
+ "domain"
1295
+ ]
1296
+ },
1297
+ "EmailDomainObjectResponse": {
1298
+ "type": "object",
1299
+ "properties": {
1300
+ "domain": {
1301
+ "type": "object",
1302
+ "properties": {
1303
+ "domain": {
1304
+ "type": "string",
1305
+ "description": "Normalized lowercase ASCII hostname for the registered custom domain."
1306
+ },
1307
+ "status": {
1308
+ "type": "string",
1309
+ "enum": [
1310
+ "pending",
1311
+ "verified",
1312
+ "failed",
1313
+ "temporary_failure"
1314
+ ],
1315
+ "description": "Overall upstream provider status for DNS ownership verification."
1316
+ },
1317
+ "sending_status": {
1318
+ "type": "string",
1319
+ "enum": [
1320
+ "pending",
1321
+ "verified",
1322
+ "failed",
1323
+ "disabled"
1324
+ ],
1325
+ "description": "Provider-side sending readiness signal for this domain registration. Custom-domain aliases can send only when this value is `verified`."
1326
+ },
1327
+ "receiving_status": {
1328
+ "type": "string",
1329
+ "enum": [
1330
+ "pending",
1331
+ "verified",
1332
+ "failed",
1333
+ "disabled"
1334
+ ],
1335
+ "description": "Provider-side receiving readiness signal for this domain registration. Custom-domain aliases can be created only when this value is `verified`."
1336
+ },
1337
+ "records": {
1338
+ "type": "array",
1339
+ "items": {
1340
+ "type": "object",
1341
+ "properties": {
1342
+ "type": {
1343
+ "type": "string",
1344
+ "enum": [
1345
+ "TXT",
1346
+ "CNAME",
1347
+ "MX"
1348
+ ],
1349
+ "description": "DNS record type required by the upstream domain provider."
1350
+ },
1351
+ "name": {
1352
+ "type": "string",
1353
+ "description": "DNS hostname to create or update."
1354
+ },
1355
+ "value": {
1356
+ "type": "string",
1357
+ "description": "Expected DNS record value."
1358
+ },
1359
+ "status": {
1360
+ "type": "string",
1361
+ "enum": [
1362
+ "pending",
1363
+ "verified",
1364
+ "failed"
1365
+ ],
1366
+ "description": "Latest provider verification status for this individual DNS record."
1367
+ },
1368
+ "purpose": {
1369
+ "type": "string",
1370
+ "enum": [
1371
+ "identity_verification",
1372
+ "dkim",
1373
+ "mail_from",
1374
+ "receiving_mx"
1375
+ ],
1376
+ "description": "Why this DNS record is required, when the provider returns a purpose hint."
1377
+ },
1378
+ "ttl": {
1379
+ "type": "integer",
1380
+ "exclusiveMinimum": 0,
1381
+ "description": "Requested TTL in seconds, if the provider specifies one."
1382
+ },
1383
+ "priority": {
1384
+ "type": "integer",
1385
+ "minimum": 0,
1386
+ "description": "MX priority, when applicable."
1387
+ }
1388
+ },
1389
+ "required": [
1390
+ "type",
1391
+ "name",
1392
+ "value",
1393
+ "status"
1394
+ ]
1395
+ },
1396
+ "description": "DNS records currently cached from the provider for ownership verification."
1397
+ },
1398
+ "region": {
1399
+ "type": "string",
1400
+ "description": "Provider region hint, when returned by the upstream provider."
1401
+ },
1402
+ "created_at": {
1403
+ "type": "number",
1404
+ "description": "Unix epoch milliseconds when the local cache row was created."
1405
+ },
1406
+ "updated_at": {
1407
+ "type": "number",
1408
+ "description": "Unix epoch milliseconds when the local cache row was last refreshed."
1409
+ },
1410
+ "verified_at": {
1411
+ "type": "number",
1412
+ "description": "Unix epoch milliseconds when the domain last reached overall `verified` status. Omitted until the first successful verification."
1413
+ }
1414
+ },
1415
+ "required": [
1416
+ "domain",
1417
+ "status",
1418
+ "sending_status",
1419
+ "receiving_status",
1420
+ "records",
1421
+ "created_at",
1422
+ "updated_at"
1423
+ ]
1424
+ }
1425
+ },
1426
+ "required": [
1427
+ "domain"
1428
+ ]
1429
+ },
1285
1430
  "BatchIdsBody": {
1286
1431
  "type": "object",
1287
1432
  "properties": {
@@ -1328,6 +1473,18 @@
1328
1473
  }
1329
1474
  }
1330
1475
  },
1476
+ "EmailDomainParam": {
1477
+ "type": "object",
1478
+ "properties": {
1479
+ "domain": {
1480
+ "type": "string",
1481
+ "description": "Custom domain hostname path parameter. URL-encode if your client requires it."
1482
+ }
1483
+ },
1484
+ "required": [
1485
+ "domain"
1486
+ ]
1487
+ },
1331
1488
  "GetEmailQuery": {
1332
1489
  "type": "object",
1333
1490
  "properties": {
@@ -1350,6 +1507,142 @@
1350
1507
  }
1351
1508
  }
1352
1509
  },
1510
+ "EmailDomainListResponse": {
1511
+ "type": "object",
1512
+ "properties": {
1513
+ "domains": {
1514
+ "type": "array",
1515
+ "items": {
1516
+ "type": "object",
1517
+ "properties": {
1518
+ "domain": {
1519
+ "type": "string",
1520
+ "description": "Normalized lowercase ASCII hostname for the registered custom domain."
1521
+ },
1522
+ "status": {
1523
+ "type": "string",
1524
+ "enum": [
1525
+ "pending",
1526
+ "verified",
1527
+ "failed",
1528
+ "temporary_failure"
1529
+ ],
1530
+ "description": "Overall upstream provider status for DNS ownership verification."
1531
+ },
1532
+ "sending_status": {
1533
+ "type": "string",
1534
+ "enum": [
1535
+ "pending",
1536
+ "verified",
1537
+ "failed",
1538
+ "disabled"
1539
+ ],
1540
+ "description": "Provider-side sending readiness signal for this domain registration. Custom-domain aliases can send only when this value is `verified`."
1541
+ },
1542
+ "receiving_status": {
1543
+ "type": "string",
1544
+ "enum": [
1545
+ "pending",
1546
+ "verified",
1547
+ "failed",
1548
+ "disabled"
1549
+ ],
1550
+ "description": "Provider-side receiving readiness signal for this domain registration. Custom-domain aliases can be created only when this value is `verified`."
1551
+ },
1552
+ "records": {
1553
+ "type": "array",
1554
+ "items": {
1555
+ "type": "object",
1556
+ "properties": {
1557
+ "type": {
1558
+ "type": "string",
1559
+ "enum": [
1560
+ "TXT",
1561
+ "CNAME",
1562
+ "MX"
1563
+ ],
1564
+ "description": "DNS record type required by the upstream domain provider."
1565
+ },
1566
+ "name": {
1567
+ "type": "string",
1568
+ "description": "DNS hostname to create or update."
1569
+ },
1570
+ "value": {
1571
+ "type": "string",
1572
+ "description": "Expected DNS record value."
1573
+ },
1574
+ "status": {
1575
+ "type": "string",
1576
+ "enum": [
1577
+ "pending",
1578
+ "verified",
1579
+ "failed"
1580
+ ],
1581
+ "description": "Latest provider verification status for this individual DNS record."
1582
+ },
1583
+ "purpose": {
1584
+ "type": "string",
1585
+ "enum": [
1586
+ "identity_verification",
1587
+ "dkim",
1588
+ "mail_from",
1589
+ "receiving_mx"
1590
+ ],
1591
+ "description": "Why this DNS record is required, when the provider returns a purpose hint."
1592
+ },
1593
+ "ttl": {
1594
+ "type": "integer",
1595
+ "exclusiveMinimum": 0,
1596
+ "description": "Requested TTL in seconds, if the provider specifies one."
1597
+ },
1598
+ "priority": {
1599
+ "type": "integer",
1600
+ "minimum": 0,
1601
+ "description": "MX priority, when applicable."
1602
+ }
1603
+ },
1604
+ "required": [
1605
+ "type",
1606
+ "name",
1607
+ "value",
1608
+ "status"
1609
+ ]
1610
+ },
1611
+ "description": "DNS records currently cached from the provider for ownership verification."
1612
+ },
1613
+ "region": {
1614
+ "type": "string",
1615
+ "description": "Provider region hint, when returned by the upstream provider."
1616
+ },
1617
+ "created_at": {
1618
+ "type": "number",
1619
+ "description": "Unix epoch milliseconds when the local cache row was created."
1620
+ },
1621
+ "updated_at": {
1622
+ "type": "number",
1623
+ "description": "Unix epoch milliseconds when the local cache row was last refreshed."
1624
+ },
1625
+ "verified_at": {
1626
+ "type": "number",
1627
+ "description": "Unix epoch milliseconds when the domain last reached overall `verified` status. Omitted until the first successful verification."
1628
+ }
1629
+ },
1630
+ "required": [
1631
+ "domain",
1632
+ "status",
1633
+ "sending_status",
1634
+ "receiving_status",
1635
+ "records",
1636
+ "created_at",
1637
+ "updated_at"
1638
+ ]
1639
+ }
1640
+ }
1641
+ },
1642
+ "required": [
1643
+ "domains"
1644
+ ]
1645
+ },
1353
1646
  "ListEmailsQuery": {
1354
1647
  "type": "object",
1355
1648
  "properties": {
@@ -15287,61 +15580,141 @@
15287
15580
  }
15288
15581
  }
15289
15582
  },
15290
- "/email/aliases/{email}": {
15291
- "delete": {
15292
- "operationId": "email_alias_delete",
15583
+ "/email/domains": {
15584
+ "post": {
15585
+ "operationId": "email_domain_create",
15293
15586
  "tags": [
15294
- "EmailAliases"
15587
+ "EmailDomains"
15295
15588
  ],
15296
- "summary": "Soft-delete an alias",
15297
- "description": "### Overview\nSoft-deletes a specific active email receiving alias owned by the caller. Once soft-deleted, the alias stops accepting and forwarding any new inbound emails.\n\n### When to Use\n- Use this endpoint when decommissioning a disposable alias address that is no longer needed or is receiving excessive spam.\n\n### Constraints\n- Requires a valid Bearer token in the `Authorization` header.\n- **Data Retention**: Soft-deletion is immediate. Inbound mail forwarding stops, but historical emails previously received on this alias remain fully readable in the inbox.\n- **Restoration**: The alias remains globally reserved and cannot be created fresh by anyone; use `POST /email/aliases/{email}/restore` to reactivate.\n- **Path Parameter**: The `@` character in the `{email}` path parameter must be URL-encoded as `%40`.\n\n### Troubleshooting\n- **401 Unauthorized**: Missing or invalid token.\n- **404 Not Found**: No active alias with this exact address was found for the authenticated user, or the alias is already deleted.",
15589
+ "summary": "Register a custom email domain",
15590
+ "description": "### Overview\nRegisters a new organization-owned custom email domain with the upstream provider and caches the returned DNS verification records in D1.\n\n### When to Use\n- Use this endpoint when onboarding a new custom email domain such as `mail.example.com`.\n- The response contains the DNS records the organization must publish before the domain can be verified.\n- This route registers the domain and returns DNS records. Custom-domain aliases require `status`, `sending_status`, and `receiving_status` to all be `verified`.\n\n### Constraints\n- Requires a valid Bearer token in the `Authorization` header.\n- Domain ownership is globally unique across the platform. Once any organization has reserved a domain, another org cannot register it.\n- This route requires custom domain provider credentials in production because it performs a live provider registration call.\n\n### Troubleshooting\n- **400 Bad Request / DOMAIN_INVALID / DOMAIN_RESERVED**: The hostname is malformed or belongs to the platform (`wspc.app`, `wspc.ai`, or their subdomains).\n- **409 Conflict / DOMAIN_CONFLICT**: The domain is already registered by some organization.\n- **502 Bad Gateway / DOMAIN_PROVIDER_ERROR**: The upstream provider request failed, timed out, or returned an unexpected shape.",
15298
15591
  "security": [
15299
15592
  {
15300
15593
  "bearerAuth": []
15301
15594
  }
15302
15595
  ],
15303
15596
  "x-cli": {
15304
- "command": "alias rm",
15597
+ "command": "domain add",
15305
15598
  "positional": [
15306
- "email"
15599
+ "domain"
15307
15600
  ],
15308
15601
  "display": {
15309
15602
  "shape": "object",
15310
15603
  "format": {
15311
- "id": "id-short",
15312
- "user_id": "id-short",
15313
15604
  "created_at": "relative-time",
15314
- "deleted_at": "relative-time"
15315
- }
15605
+ "updated_at": "relative-time",
15606
+ "verified_at": "relative-time"
15607
+ },
15608
+ "dataPath": "domain"
15316
15609
  }
15317
15610
  },
15318
15611
  "x-codeSamples": [
15319
15612
  {
15320
15613
  "lang": "shell",
15321
15614
  "label": "curl",
15322
- "source": "curl -X DELETE https://api.wspc.ai/email/aliases/alice-shop%40wspc.app \\\n -H \"Authorization: Bearer $WSPC_API_KEY\""
15615
+ "source": "curl -X POST https://api.wspc.ai/email/domains \\\n -H \"Authorization: Bearer $WSPC_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"domain\":\"mail.example.com\"}'"
15323
15616
  },
15324
15617
  {
15325
15618
  "lang": "bash",
15326
15619
  "label": "wspc CLI",
15327
- "source": "wspc email alias rm alice-shop@wspc.app"
15620
+ "source": "wspc email domain add mail.example.com"
15328
15621
  }
15329
15622
  ],
15330
- "parameters": [
15331
- {
15332
- "schema": {
15333
- "type": "string",
15334
- "description": "Full alias email address. URL-encode @ as %40 in paths."
15335
- },
15336
- "required": true,
15337
- "description": "Full alias email address. URL-encode @ as %40 in paths.",
15338
- "name": "email",
15339
- "in": "path"
15623
+ "requestBody": {
15624
+ "required": true,
15625
+ "content": {
15626
+ "application/json": {
15627
+ "schema": {
15628
+ "$ref": "#/components/schemas/CreateDomainBody"
15629
+ },
15630
+ "examples": {
15631
+ "minimal": {
15632
+ "summary": "Register one custom domain",
15633
+ "value": {
15634
+ "domain": "mail.example.com"
15635
+ }
15636
+ }
15637
+ }
15638
+ }
15340
15639
  }
15341
- ],
15640
+ },
15342
15641
  "responses": {
15343
- "204": {
15344
- "description": "Alias soft-deleted. Forwarding stops immediately. No response body."
15642
+ "201": {
15643
+ "description": "Custom domain registered and DNS verification records returned. Custom-domain aliases require full domain readiness before use.",
15644
+ "content": {
15645
+ "application/json": {
15646
+ "schema": {
15647
+ "$ref": "#/components/schemas/EmailDomainObjectResponse"
15648
+ },
15649
+ "examples": {
15650
+ "happyPath": {
15651
+ "summary": "Pending verification with DNS instructions",
15652
+ "value": {
15653
+ "domain": {
15654
+ "domain": "mail.example.com",
15655
+ "status": "pending",
15656
+ "sending_status": "pending",
15657
+ "receiving_status": "pending",
15658
+ "records": [
15659
+ {
15660
+ "type": "TXT",
15661
+ "name": "mail.example.com",
15662
+ "value": "pm-domain-verification=abc123",
15663
+ "status": "pending",
15664
+ "ttl": 3600,
15665
+ "purpose": "identity_verification"
15666
+ },
15667
+ {
15668
+ "type": "CNAME",
15669
+ "name": "selector1._domainkey.mail.example.com",
15670
+ "value": "selector1.domainkey.example.net",
15671
+ "status": "pending",
15672
+ "purpose": "dkim"
15673
+ }
15674
+ ],
15675
+ "region": "us-east-1",
15676
+ "created_at": 1780166400000,
15677
+ "updated_at": 1780166400000
15678
+ }
15679
+ }
15680
+ }
15681
+ }
15682
+ }
15683
+ }
15684
+ },
15685
+ "400": {
15686
+ "description": "Malformed or platform-reserved domain hostname.",
15687
+ "content": {
15688
+ "application/json": {
15689
+ "schema": {
15690
+ "type": "object",
15691
+ "properties": {
15692
+ "error": {
15693
+ "type": "object",
15694
+ "properties": {
15695
+ "code": {
15696
+ "type": "string"
15697
+ },
15698
+ "message": {
15699
+ "type": "string"
15700
+ },
15701
+ "extra": {
15702
+ "type": "object",
15703
+ "additionalProperties": {}
15704
+ }
15705
+ },
15706
+ "required": [
15707
+ "code",
15708
+ "message"
15709
+ ]
15710
+ }
15711
+ },
15712
+ "required": [
15713
+ "error"
15714
+ ]
15715
+ }
15716
+ }
15717
+ }
15345
15718
  },
15346
15719
  "401": {
15347
15720
  "description": "Authentication is required but missing or invalid. The Bearer token (API key or OAuth access token) was absent, malformed, or rejected.",
@@ -15388,8 +15761,8 @@
15388
15761
  }
15389
15762
  }
15390
15763
  },
15391
- "404": {
15392
- "description": "No active alias with this email address is owned by the caller.",
15764
+ "409": {
15765
+ "description": "This domain is already registered by an organization.",
15393
15766
  "content": {
15394
15767
  "application/json": {
15395
15768
  "schema": {
@@ -15515,112 +15888,9 @@
15515
15888
  }
15516
15889
  }
15517
15890
  }
15518
- }
15519
- }
15520
- }
15521
- },
15522
- "/email/messages/delete": {
15523
- "post": {
15524
- "operationId": "email_delete",
15525
- "tags": [
15526
- "Emails"
15527
- ],
15528
- "summary": "Soft-delete inbound emails",
15529
- "description": "### Overview\nSoft-deletes a batch of inbound emails, moving them to the trash. Soft-deleted emails are immediately excluded from default inbox lists.\n\n### When to Use\n- Use this endpoint to trash one or more email messages from a user's inbox view.\n\n### Constraints\n- Requires a valid Bearer token in the `Authorization` header.\n- Accepts 1 to 100 email IDs per call.\n- Deletion is fully reversible: soft-deleted rows persist in the database and can be undeleted using the restore endpoint.\n- **Data Cleanup**: Out-of-band background processes eventually purge associated raw MIME source payloads and attachment bytes from R2; deletion does not immediately free storage.\n\n### Troubleshooting\n- **401 Unauthorized**: Invalid Bearer token.\n- **400 Bad Request**: The request body is malformed or exceeds the maximum limit of 100 IDs.",
15530
- "security": [
15531
- {
15532
- "bearerAuth": []
15533
- }
15534
- ],
15535
- "x-cli": {
15536
- "command": "email rm",
15537
- "positional": [
15538
- "id"
15539
- ],
15540
- "options": {
15541
- "id": {
15542
- "array": true,
15543
- "mapsTo": "ids"
15544
- }
15545
- },
15546
- "display": {
15547
- "shape": "object",
15548
- "format": {}
15549
- }
15550
- },
15551
- "x-codeSamples": [
15552
- {
15553
- "lang": "shell",
15554
- "label": "curl",
15555
- "source": "curl -X POST https://api.wspc.ai/email/messages/delete \\\n -H \"Authorization: Bearer $WSPC_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"ids\":[\"eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F\",\"eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3G\"]}'"
15556
- },
15557
- {
15558
- "lang": "bash",
15559
- "label": "wspc CLI",
15560
- "source": "wspc email rm eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3G"
15561
- }
15562
- ],
15563
- "requestBody": {
15564
- "required": true,
15565
- "content": {
15566
- "application/json": {
15567
- "schema": {
15568
- "$ref": "#/components/schemas/BatchIdsBody"
15569
- },
15570
- "examples": {
15571
- "minimal": {
15572
- "summary": "Single id",
15573
- "value": {
15574
- "ids": [
15575
- "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F"
15576
- ]
15577
- }
15578
- },
15579
- "full": {
15580
- "summary": "Multiple ids (up to 100 per call)",
15581
- "value": {
15582
- "ids": [
15583
- "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F",
15584
- "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3G",
15585
- "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3H"
15586
- ]
15587
- }
15588
- }
15589
- }
15590
- }
15591
- }
15592
- },
15593
- "responses": {
15594
- "200": {
15595
- "description": "Bulk soft-delete result. `deleted` counts state changes only.",
15596
- "content": {
15597
- "application/json": {
15598
- "schema": {
15599
- "$ref": "#/components/schemas/DeleteBatchResponse"
15600
- },
15601
- "examples": {
15602
- "happyPath": {
15603
- "summary": "All ids matched and changed state",
15604
- "value": {
15605
- "deleted": 2,
15606
- "not_found": []
15607
- }
15608
- },
15609
- "partial": {
15610
- "summary": "Some ids unknown to this user",
15611
- "value": {
15612
- "deleted": 1,
15613
- "not_found": [
15614
- "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3X"
15615
- ]
15616
- }
15617
- }
15618
- }
15619
- }
15620
- }
15621
15891
  },
15622
- "400": {
15623
- "description": "Request validation failed. The body, query, or path parameters did not match the operation's schema.",
15892
+ "502": {
15893
+ "description": "The upstream provider call failed or provider credentials are missing.",
15624
15894
  "content": {
15625
15895
  "application/json": {
15626
15896
  "schema": {
@@ -15649,15 +15919,128 @@
15649
15919
  "required": [
15650
15920
  "error"
15651
15921
  ]
15922
+ }
15923
+ }
15924
+ }
15925
+ }
15926
+ }
15927
+ },
15928
+ "get": {
15929
+ "operationId": "email_domain_list",
15930
+ "tags": [
15931
+ "EmailDomains"
15932
+ ],
15933
+ "summary": "List cached custom domains",
15934
+ "description": "### Overview\nReturns the caller organization's cached custom email domains from D1. This route does not call the upstream provider.\n\n### When to Use\n- Use this to render an admin view of all registered domains and their latest known verification state.\n- Use it to inspect DNS records that were previously fetched during create or verify operations.\n- The cached state includes DNS ownership, sending readiness, and receiving readiness used by custom-domain alias creation.\n\n### Constraints\n- Requires a valid Bearer token in the `Authorization` header.\n- Results are scoped to the caller organization and sorted newest-first by creation time.",
15935
+ "security": [
15936
+ {
15937
+ "bearerAuth": []
15938
+ }
15939
+ ],
15940
+ "x-cli": {
15941
+ "command": "domain ls",
15942
+ "display": {
15943
+ "shape": "list",
15944
+ "columns": [
15945
+ "domain",
15946
+ "status",
15947
+ "sending_status",
15948
+ "receiving_status",
15949
+ "updated_at"
15950
+ ],
15951
+ "format": {
15952
+ "updated_at": "relative-time",
15953
+ "verified_at": "relative-time"
15954
+ },
15955
+ "emptyMessage": "no domains",
15956
+ "dataPath": "domains"
15957
+ }
15958
+ },
15959
+ "x-codeSamples": [
15960
+ {
15961
+ "lang": "shell",
15962
+ "label": "curl",
15963
+ "source": "curl https://api.wspc.ai/email/domains \\\n -H \"Authorization: Bearer $WSPC_API_KEY\""
15964
+ },
15965
+ {
15966
+ "lang": "bash",
15967
+ "label": "wspc CLI",
15968
+ "source": "wspc email domain ls"
15969
+ }
15970
+ ],
15971
+ "responses": {
15972
+ "200": {
15973
+ "description": "Custom domains cached for the caller organization, including ownership, sending readiness, and receiving readiness state.",
15974
+ "content": {
15975
+ "application/json": {
15976
+ "schema": {
15977
+ "$ref": "#/components/schemas/EmailDomainListResponse"
15652
15978
  },
15653
15979
  "examples": {
15654
- "validationError": {
15655
- "summary": "Body failed schema validation",
15980
+ "happyPath": {
15981
+ "summary": "Two cached domains",
15656
15982
  "value": {
15657
- "error": {
15658
- "code": "VALIDATION_ERROR",
15659
- "message": "title must not be empty"
15660
- }
15983
+ "domains": [
15984
+ {
15985
+ "domain": "mail.example.com",
15986
+ "status": "pending",
15987
+ "sending_status": "pending",
15988
+ "receiving_status": "pending",
15989
+ "records": [
15990
+ {
15991
+ "type": "TXT",
15992
+ "name": "mail.example.com",
15993
+ "value": "pm-domain-verification=abc123",
15994
+ "status": "pending",
15995
+ "ttl": 3600,
15996
+ "purpose": "identity_verification"
15997
+ },
15998
+ {
15999
+ "type": "CNAME",
16000
+ "name": "selector1._domainkey.mail.example.com",
16001
+ "value": "selector1.domainkey.example.net",
16002
+ "status": "pending",
16003
+ "purpose": "dkim"
16004
+ }
16005
+ ],
16006
+ "region": "us-east-1",
16007
+ "created_at": 1780166400000,
16008
+ "updated_at": 1780166400000
16009
+ },
16010
+ {
16011
+ "domain": "reply.example.com",
16012
+ "status": "verified",
16013
+ "sending_status": "verified",
16014
+ "receiving_status": "disabled",
16015
+ "records": [
16016
+ {
16017
+ "type": "TXT",
16018
+ "name": "mail.example.com",
16019
+ "value": "pm-domain-verification=abc123",
16020
+ "status": "verified",
16021
+ "ttl": 3600,
16022
+ "purpose": "identity_verification"
16023
+ },
16024
+ {
16025
+ "type": "CNAME",
16026
+ "name": "selector1._domainkey.mail.example.com",
16027
+ "value": "selector1.domainkey.example.net",
16028
+ "status": "verified",
16029
+ "purpose": "dkim"
16030
+ }
16031
+ ],
16032
+ "region": "us-east-1",
16033
+ "created_at": 1780166400000,
16034
+ "updated_at": 1780167000000,
16035
+ "verified_at": 1780167000000
16036
+ }
16037
+ ]
16038
+ }
16039
+ },
16040
+ "empty": {
16041
+ "summary": "No custom domains yet",
16042
+ "value": {
16043
+ "domains": []
15661
16044
  }
15662
16045
  }
15663
16046
  }
@@ -15806,73 +16189,61 @@
15806
16189
  }
15807
16190
  }
15808
16191
  },
15809
- "/email/messages/{id}/attachments/{idx}": {
15810
- "get": {
15811
- "operationId": "email_attachment_get",
16192
+ "/email/aliases/{email}": {
16193
+ "delete": {
16194
+ "operationId": "email_alias_delete",
15812
16195
  "tags": [
15813
- "Emails"
16196
+ "EmailAliases"
15814
16197
  ],
15815
- "summary": "Download an attachment by index",
15816
- "description": "### Overview\nStreams the raw decoded bytes of a parsed attachment belonging to an inbound email. The response body is binary data instead of JSON.\n\n### When to Use\n- Use this endpoint when a user clicks to download a file attachment (such as an invoice PDF or image) or when an automated agent needs to process a file payload.\n\n### Constraints\n- Requires a valid Bearer token in the `Authorization` header.\n- **Response Headers**: The server sets the HTTP `Content-Type` matching the attachment's parsed MIME format and provides a `Content-Disposition: attachment; filename=\"<filename>\"` header.\n- **Soft-Deleted Parents**: Downloading files from soft-deleted emails is blocked with a 404 error, unless the query parameter `include_deleted=true` is provided.\n- **Path Parameter**: The `{idx}` must be a valid 0-based integer index pointing to the attachment list metadata.\n\n### Troubleshooting\n- **401 Unauthorized**: Invalid Bearer token.\n- **404 Not Found / EMAIL_NOT_FOUND**: The specified email ID does not exist or belongs to another user.\n- **404 Not Found / ATTACHMENT_NOT_FOUND**: The index `{idx}` is out of range for the email's attachment array.",
16198
+ "summary": "Soft-delete an alias",
16199
+ "description": "### Overview\nSoft-deletes a specific active email receiving alias owned by the caller. Once soft-deleted, the alias stops accepting and forwarding any new inbound emails.\n\n### When to Use\n- Use this endpoint when decommissioning a disposable alias address that is no longer needed or is receiving excessive spam.\n\n### Constraints\n- Requires a valid Bearer token in the `Authorization` header.\n- **Data Retention**: Soft-deletion is immediate. Inbound mail forwarding stops, but historical emails previously received on this alias remain fully readable in the inbox.\n- **Restoration**: The alias remains globally reserved and cannot be created fresh by anyone; use `POST /email/aliases/{email}/restore` to reactivate.\n- **Path Parameter**: The `@` character in the `{email}` path parameter must be URL-encoded as `%40`.\n\n### Troubleshooting\n- **401 Unauthorized**: Missing or invalid token.\n- **404 Not Found**: No active alias with this exact address was found for the authenticated user, or the alias is already deleted.",
15817
16200
  "security": [
15818
16201
  {
15819
16202
  "bearerAuth": []
15820
16203
  }
15821
16204
  ],
15822
16205
  "x-cli": {
15823
- "command": "_handwritten",
15824
- "hidden": true
16206
+ "command": "alias rm",
16207
+ "positional": [
16208
+ "email"
16209
+ ],
16210
+ "display": {
16211
+ "shape": "object",
16212
+ "format": {
16213
+ "id": "id-short",
16214
+ "user_id": "id-short",
16215
+ "created_at": "relative-time",
16216
+ "deleted_at": "relative-time"
16217
+ }
16218
+ }
15825
16219
  },
15826
16220
  "x-codeSamples": [
15827
16221
  {
15828
16222
  "lang": "shell",
15829
16223
  "label": "curl",
15830
- "source": "curl https://api.wspc.ai/email/messages/eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F/attachments/0 \\\n -H \"Authorization: Bearer $WSPC_API_KEY\" \\\n -o invoice.pdf"
16224
+ "source": "curl -X DELETE https://api.wspc.ai/email/aliases/alice-shop%40wspc.app \\\n -H \"Authorization: Bearer $WSPC_API_KEY\""
15831
16225
  },
15832
16226
  {
15833
16227
  "lang": "bash",
15834
16228
  "label": "wspc CLI",
15835
- "source": "wspc email attachment eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F 0 --output invoice.pdf"
16229
+ "source": "wspc email alias rm alice-shop@wspc.app"
15836
16230
  }
15837
16231
  ],
15838
16232
  "parameters": [
15839
16233
  {
15840
16234
  "schema": {
15841
- "type": "string"
15842
- },
15843
- "required": true,
15844
- "name": "id",
15845
- "in": "path"
15846
- },
15847
- {
15848
- "schema": {
15849
- "type": "string"
16235
+ "type": "string",
16236
+ "description": "Full alias email address. URL-encode @ as %40 in paths."
15850
16237
  },
15851
16238
  "required": true,
15852
- "name": "idx",
16239
+ "description": "Full alias email address. URL-encode @ as %40 in paths.",
16240
+ "name": "email",
15853
16241
  "in": "path"
15854
- },
15855
- {
15856
- "schema": {
15857
- "type": "string",
15858
- "description": "When `true`, allow downloading an attachment whose parent email is soft-deleted. Defaults to `false`."
15859
- },
15860
- "required": false,
15861
- "description": "When `true`, allow downloading an attachment whose parent email is soft-deleted. Defaults to `false`.",
15862
- "name": "include_deleted",
15863
- "in": "query"
15864
16242
  }
15865
16243
  ],
15866
16244
  "responses": {
15867
- "200": {
15868
- "description": "Raw attachment bytes. `Content-Type` matches the attachment's parsed MIME (often `application/pdf`, `image/jpeg`, `text/calendar`, or `application/octet-stream` when unknown). `Content-Disposition: attachment; filename=\"<filename>\"`.",
15869
- "content": {
15870
- "application/octet-stream": {
15871
- "schema": {
15872
- "type": "string"
15873
- }
15874
- }
15875
- }
16245
+ "204": {
16246
+ "description": "Alias soft-deleted. Forwarding stops immediately. No response body."
15876
16247
  },
15877
16248
  "401": {
15878
16249
  "description": "Authentication is required but missing or invalid. The Bearer token (API key or OAuth access token) was absent, malformed, or rejected.",
@@ -15920,7 +16291,7 @@
15920
16291
  }
15921
16292
  },
15922
16293
  "404": {
15923
- "description": "Either the parent email is unknown / soft-deleted (`EMAIL_NOT_FOUND`) or `idx` is out of range (`ATTACHMENT_NOT_FOUND`).",
16294
+ "description": "No active alias with this email address is owned by the caller.",
15924
16295
  "content": {
15925
16296
  "application/json": {
15926
16297
  "schema": {
@@ -16050,55 +16421,892 @@
16050
16421
  }
16051
16422
  }
16052
16423
  },
16053
- "/email/messages/{id}": {
16054
- "get": {
16055
- "operationId": "email_get",
16424
+ "/email/messages/delete": {
16425
+ "post": {
16426
+ "operationId": "email_delete",
16056
16427
  "tags": [
16057
16428
  "Emails"
16058
16429
  ],
16059
- "summary": "Get an inbound email by id",
16060
- "description": "### Overview\nFetches the metadata and plain-text body of a single inbound email by its unique ID. It also returns metadata for all associated attachments and optionally resolves the rendered HTML content.\n\n### When to Use\n- Use this endpoint to display the complete detail view of an email message.\n- Use it to extract attachment files or read complex HTML layouts.\n\n### Constraints\n- Requires a valid Bearer token in the `Authorization` header.\n- **R2 HTML Read**: The HTML body is stored in Object Storage (R2). To fetch it, explicitly pass `include_html=true` (this incurs an extra R2 read charge; leave unset if only plain text is needed).\n- Returns a 404 error if the email has been soft-deleted, unless `include_deleted=true` is set.\n\n### Troubleshooting\n- **401 Unauthorized**: Missing or expired token.\n- **404 Not Found**: The specified email ID does not exist, belongs to another user, or has been soft-deleted (without `include_deleted=true`).",
16430
+ "summary": "Soft-delete inbound emails",
16431
+ "description": "### Overview\nSoft-deletes a batch of inbound emails, moving them to the trash. Soft-deleted emails are immediately excluded from default inbox lists.\n\n### When to Use\n- Use this endpoint to trash one or more email messages from a user's inbox view.\n\n### Constraints\n- Requires a valid Bearer token in the `Authorization` header.\n- Accepts 1 to 100 email IDs per call.\n- Deletion is fully reversible: soft-deleted rows persist in the database and can be undeleted using the restore endpoint.\n- **Data Cleanup**: Out-of-band background processes eventually purge associated raw MIME source payloads and attachment bytes from R2; deletion does not immediately free storage.\n\n### Troubleshooting\n- **401 Unauthorized**: Invalid Bearer token.\n- **400 Bad Request**: The request body is malformed or exceeds the maximum limit of 100 IDs.",
16061
16432
  "security": [
16062
16433
  {
16063
16434
  "bearerAuth": []
16064
16435
  }
16065
16436
  ],
16066
16437
  "x-cli": {
16067
- "command": "email show",
16438
+ "command": "email rm",
16068
16439
  "positional": [
16069
16440
  "id"
16070
16441
  ],
16442
+ "options": {
16443
+ "id": {
16444
+ "array": true,
16445
+ "mapsTo": "ids"
16446
+ }
16447
+ },
16071
16448
  "display": {
16072
16449
  "shape": "object",
16073
- "format": {
16074
- "id": "id-short",
16075
- "org_id": "id-short",
16076
- "user_id": "id-short",
16077
- "received_at": "relative-time",
16078
- "created_at": "relative-time",
16079
- "read_at": "relative-time",
16080
- "deleted_at": "relative-time",
16081
- "is_read": "bool-badge"
16082
- },
16083
- "dataPath": "email"
16450
+ "format": {}
16084
16451
  }
16085
16452
  },
16086
16453
  "x-codeSamples": [
16087
16454
  {
16088
16455
  "lang": "shell",
16089
16456
  "label": "curl",
16090
- "source": "curl https://api.wspc.ai/email/messages/eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F \\\n -H \"Authorization: Bearer $WSPC_API_KEY\""
16457
+ "source": "curl -X POST https://api.wspc.ai/email/messages/delete \\\n -H \"Authorization: Bearer $WSPC_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"ids\":[\"eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F\",\"eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3G\"]}'"
16091
16458
  },
16092
16459
  {
16093
16460
  "lang": "bash",
16094
16461
  "label": "wspc CLI",
16095
- "source": "wspc email show eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F"
16462
+ "source": "wspc email rm eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3G"
16096
16463
  }
16097
16464
  ],
16098
- "parameters": [
16099
- {
16100
- "schema": {
16101
- "type": "string"
16465
+ "requestBody": {
16466
+ "required": true,
16467
+ "content": {
16468
+ "application/json": {
16469
+ "schema": {
16470
+ "$ref": "#/components/schemas/BatchIdsBody"
16471
+ },
16472
+ "examples": {
16473
+ "minimal": {
16474
+ "summary": "Single id",
16475
+ "value": {
16476
+ "ids": [
16477
+ "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F"
16478
+ ]
16479
+ }
16480
+ },
16481
+ "full": {
16482
+ "summary": "Multiple ids (up to 100 per call)",
16483
+ "value": {
16484
+ "ids": [
16485
+ "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F",
16486
+ "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3G",
16487
+ "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3H"
16488
+ ]
16489
+ }
16490
+ }
16491
+ }
16492
+ }
16493
+ }
16494
+ },
16495
+ "responses": {
16496
+ "200": {
16497
+ "description": "Bulk soft-delete result. `deleted` counts state changes only.",
16498
+ "content": {
16499
+ "application/json": {
16500
+ "schema": {
16501
+ "$ref": "#/components/schemas/DeleteBatchResponse"
16502
+ },
16503
+ "examples": {
16504
+ "happyPath": {
16505
+ "summary": "All ids matched and changed state",
16506
+ "value": {
16507
+ "deleted": 2,
16508
+ "not_found": []
16509
+ }
16510
+ },
16511
+ "partial": {
16512
+ "summary": "Some ids unknown to this user",
16513
+ "value": {
16514
+ "deleted": 1,
16515
+ "not_found": [
16516
+ "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3X"
16517
+ ]
16518
+ }
16519
+ }
16520
+ }
16521
+ }
16522
+ }
16523
+ },
16524
+ "400": {
16525
+ "description": "Request validation failed. The body, query, or path parameters did not match the operation's schema.",
16526
+ "content": {
16527
+ "application/json": {
16528
+ "schema": {
16529
+ "type": "object",
16530
+ "properties": {
16531
+ "error": {
16532
+ "type": "object",
16533
+ "properties": {
16534
+ "code": {
16535
+ "type": "string"
16536
+ },
16537
+ "message": {
16538
+ "type": "string"
16539
+ },
16540
+ "extra": {
16541
+ "type": "object",
16542
+ "additionalProperties": {}
16543
+ }
16544
+ },
16545
+ "required": [
16546
+ "code",
16547
+ "message"
16548
+ ]
16549
+ }
16550
+ },
16551
+ "required": [
16552
+ "error"
16553
+ ]
16554
+ },
16555
+ "examples": {
16556
+ "validationError": {
16557
+ "summary": "Body failed schema validation",
16558
+ "value": {
16559
+ "error": {
16560
+ "code": "VALIDATION_ERROR",
16561
+ "message": "title must not be empty"
16562
+ }
16563
+ }
16564
+ }
16565
+ }
16566
+ }
16567
+ }
16568
+ },
16569
+ "401": {
16570
+ "description": "Authentication is required but missing or invalid. The Bearer token (API key or OAuth access token) was absent, malformed, or rejected.",
16571
+ "content": {
16572
+ "application/json": {
16573
+ "schema": {
16574
+ "type": "object",
16575
+ "properties": {
16576
+ "error": {
16577
+ "type": "object",
16578
+ "properties": {
16579
+ "code": {
16580
+ "type": "string"
16581
+ },
16582
+ "message": {
16583
+ "type": "string"
16584
+ },
16585
+ "extra": {
16586
+ "type": "object",
16587
+ "additionalProperties": {}
16588
+ }
16589
+ },
16590
+ "required": [
16591
+ "code",
16592
+ "message"
16593
+ ]
16594
+ }
16595
+ },
16596
+ "required": [
16597
+ "error"
16598
+ ]
16599
+ },
16600
+ "examples": {
16601
+ "authRequired": {
16602
+ "summary": "Missing Authorization header",
16603
+ "value": {
16604
+ "error": {
16605
+ "code": "AUTH_REQUIRED",
16606
+ "message": "missing bearer token"
16607
+ }
16608
+ }
16609
+ }
16610
+ }
16611
+ }
16612
+ }
16613
+ },
16614
+ "429": {
16615
+ "description": "Rate limit exceeded. Retry after the duration in `error.extra.retry_after_seconds`. `limit_kind` identifies which bucket was exhausted.",
16616
+ "content": {
16617
+ "application/json": {
16618
+ "schema": {
16619
+ "type": "object",
16620
+ "properties": {
16621
+ "error": {
16622
+ "type": "object",
16623
+ "properties": {
16624
+ "code": {
16625
+ "type": "string"
16626
+ },
16627
+ "message": {
16628
+ "type": "string"
16629
+ },
16630
+ "extra": {
16631
+ "type": "object",
16632
+ "additionalProperties": {}
16633
+ }
16634
+ },
16635
+ "required": [
16636
+ "code",
16637
+ "message"
16638
+ ]
16639
+ }
16640
+ },
16641
+ "required": [
16642
+ "error"
16643
+ ]
16644
+ },
16645
+ "examples": {
16646
+ "rateLimited": {
16647
+ "summary": "Per-key rate limit hit",
16648
+ "value": {
16649
+ "error": {
16650
+ "code": "RATE_LIMITED",
16651
+ "message": "rate limit exceeded",
16652
+ "extra": {
16653
+ "retry_after_seconds": 60,
16654
+ "limit_kind": "authenticated_per_key"
16655
+ }
16656
+ }
16657
+ }
16658
+ }
16659
+ }
16660
+ }
16661
+ }
16662
+ },
16663
+ "500": {
16664
+ "description": "Unhandled server error. The request was well-formed but the service failed unexpectedly. Safe to retry idempotent operations.",
16665
+ "content": {
16666
+ "application/json": {
16667
+ "schema": {
16668
+ "type": "object",
16669
+ "properties": {
16670
+ "error": {
16671
+ "type": "object",
16672
+ "properties": {
16673
+ "code": {
16674
+ "type": "string"
16675
+ },
16676
+ "message": {
16677
+ "type": "string"
16678
+ },
16679
+ "extra": {
16680
+ "type": "object",
16681
+ "additionalProperties": {}
16682
+ }
16683
+ },
16684
+ "required": [
16685
+ "code",
16686
+ "message"
16687
+ ]
16688
+ }
16689
+ },
16690
+ "required": [
16691
+ "error"
16692
+ ]
16693
+ },
16694
+ "examples": {
16695
+ "internalError": {
16696
+ "summary": "Unhandled exception",
16697
+ "value": {
16698
+ "error": {
16699
+ "code": "INTERNAL_ERROR",
16700
+ "message": "internal error"
16701
+ }
16702
+ }
16703
+ }
16704
+ }
16705
+ }
16706
+ }
16707
+ }
16708
+ }
16709
+ }
16710
+ },
16711
+ "/email/messages/{id}/attachments/{idx}": {
16712
+ "get": {
16713
+ "operationId": "email_attachment_get",
16714
+ "tags": [
16715
+ "Emails"
16716
+ ],
16717
+ "summary": "Download an attachment by index",
16718
+ "description": "### Overview\nStreams the raw decoded bytes of a parsed attachment belonging to an inbound email. The response body is binary data instead of JSON.\n\n### When to Use\n- Use this endpoint when a user clicks to download a file attachment (such as an invoice PDF or image) or when an automated agent needs to process a file payload.\n\n### Constraints\n- Requires a valid Bearer token in the `Authorization` header.\n- **Response Headers**: The server sets the HTTP `Content-Type` matching the attachment's parsed MIME format and provides a `Content-Disposition: attachment; filename=\"<filename>\"` header.\n- **Soft-Deleted Parents**: Downloading files from soft-deleted emails is blocked with a 404 error, unless the query parameter `include_deleted=true` is provided.\n- **Path Parameter**: The `{idx}` must be a valid 0-based integer index pointing to the attachment list metadata.\n\n### Troubleshooting\n- **401 Unauthorized**: Invalid Bearer token.\n- **404 Not Found / EMAIL_NOT_FOUND**: The specified email ID does not exist or belongs to another user.\n- **404 Not Found / ATTACHMENT_NOT_FOUND**: The index `{idx}` is out of range for the email's attachment array.",
16719
+ "security": [
16720
+ {
16721
+ "bearerAuth": []
16722
+ }
16723
+ ],
16724
+ "x-cli": {
16725
+ "command": "_handwritten",
16726
+ "hidden": true
16727
+ },
16728
+ "x-codeSamples": [
16729
+ {
16730
+ "lang": "shell",
16731
+ "label": "curl",
16732
+ "source": "curl https://api.wspc.ai/email/messages/eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F/attachments/0 \\\n -H \"Authorization: Bearer $WSPC_API_KEY\" \\\n -o invoice.pdf"
16733
+ },
16734
+ {
16735
+ "lang": "bash",
16736
+ "label": "wspc CLI",
16737
+ "source": "wspc email attachment eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F 0 --output invoice.pdf"
16738
+ }
16739
+ ],
16740
+ "parameters": [
16741
+ {
16742
+ "schema": {
16743
+ "type": "string"
16744
+ },
16745
+ "required": true,
16746
+ "name": "id",
16747
+ "in": "path"
16748
+ },
16749
+ {
16750
+ "schema": {
16751
+ "type": "string"
16752
+ },
16753
+ "required": true,
16754
+ "name": "idx",
16755
+ "in": "path"
16756
+ },
16757
+ {
16758
+ "schema": {
16759
+ "type": "string",
16760
+ "description": "When `true`, allow downloading an attachment whose parent email is soft-deleted. Defaults to `false`."
16761
+ },
16762
+ "required": false,
16763
+ "description": "When `true`, allow downloading an attachment whose parent email is soft-deleted. Defaults to `false`.",
16764
+ "name": "include_deleted",
16765
+ "in": "query"
16766
+ }
16767
+ ],
16768
+ "responses": {
16769
+ "200": {
16770
+ "description": "Raw attachment bytes. `Content-Type` matches the attachment's parsed MIME (often `application/pdf`, `image/jpeg`, `text/calendar`, or `application/octet-stream` when unknown). `Content-Disposition: attachment; filename=\"<filename>\"`.",
16771
+ "content": {
16772
+ "application/octet-stream": {
16773
+ "schema": {
16774
+ "type": "string"
16775
+ }
16776
+ }
16777
+ }
16778
+ },
16779
+ "401": {
16780
+ "description": "Authentication is required but missing or invalid. The Bearer token (API key or OAuth access token) was absent, malformed, or rejected.",
16781
+ "content": {
16782
+ "application/json": {
16783
+ "schema": {
16784
+ "type": "object",
16785
+ "properties": {
16786
+ "error": {
16787
+ "type": "object",
16788
+ "properties": {
16789
+ "code": {
16790
+ "type": "string"
16791
+ },
16792
+ "message": {
16793
+ "type": "string"
16794
+ },
16795
+ "extra": {
16796
+ "type": "object",
16797
+ "additionalProperties": {}
16798
+ }
16799
+ },
16800
+ "required": [
16801
+ "code",
16802
+ "message"
16803
+ ]
16804
+ }
16805
+ },
16806
+ "required": [
16807
+ "error"
16808
+ ]
16809
+ },
16810
+ "examples": {
16811
+ "authRequired": {
16812
+ "summary": "Missing Authorization header",
16813
+ "value": {
16814
+ "error": {
16815
+ "code": "AUTH_REQUIRED",
16816
+ "message": "missing bearer token"
16817
+ }
16818
+ }
16819
+ }
16820
+ }
16821
+ }
16822
+ }
16823
+ },
16824
+ "404": {
16825
+ "description": "Either the parent email is unknown / soft-deleted (`EMAIL_NOT_FOUND`) or `idx` is out of range (`ATTACHMENT_NOT_FOUND`).",
16826
+ "content": {
16827
+ "application/json": {
16828
+ "schema": {
16829
+ "type": "object",
16830
+ "properties": {
16831
+ "error": {
16832
+ "type": "object",
16833
+ "properties": {
16834
+ "code": {
16835
+ "type": "string"
16836
+ },
16837
+ "message": {
16838
+ "type": "string"
16839
+ },
16840
+ "extra": {
16841
+ "type": "object",
16842
+ "additionalProperties": {}
16843
+ }
16844
+ },
16845
+ "required": [
16846
+ "code",
16847
+ "message"
16848
+ ]
16849
+ }
16850
+ },
16851
+ "required": [
16852
+ "error"
16853
+ ]
16854
+ }
16855
+ }
16856
+ }
16857
+ },
16858
+ "429": {
16859
+ "description": "Rate limit exceeded. Retry after the duration in `error.extra.retry_after_seconds`. `limit_kind` identifies which bucket was exhausted.",
16860
+ "content": {
16861
+ "application/json": {
16862
+ "schema": {
16863
+ "type": "object",
16864
+ "properties": {
16865
+ "error": {
16866
+ "type": "object",
16867
+ "properties": {
16868
+ "code": {
16869
+ "type": "string"
16870
+ },
16871
+ "message": {
16872
+ "type": "string"
16873
+ },
16874
+ "extra": {
16875
+ "type": "object",
16876
+ "additionalProperties": {}
16877
+ }
16878
+ },
16879
+ "required": [
16880
+ "code",
16881
+ "message"
16882
+ ]
16883
+ }
16884
+ },
16885
+ "required": [
16886
+ "error"
16887
+ ]
16888
+ },
16889
+ "examples": {
16890
+ "rateLimited": {
16891
+ "summary": "Per-key rate limit hit",
16892
+ "value": {
16893
+ "error": {
16894
+ "code": "RATE_LIMITED",
16895
+ "message": "rate limit exceeded",
16896
+ "extra": {
16897
+ "retry_after_seconds": 60,
16898
+ "limit_kind": "authenticated_per_key"
16899
+ }
16900
+ }
16901
+ }
16902
+ }
16903
+ }
16904
+ }
16905
+ }
16906
+ },
16907
+ "500": {
16908
+ "description": "Unhandled server error. The request was well-formed but the service failed unexpectedly. Safe to retry idempotent operations.",
16909
+ "content": {
16910
+ "application/json": {
16911
+ "schema": {
16912
+ "type": "object",
16913
+ "properties": {
16914
+ "error": {
16915
+ "type": "object",
16916
+ "properties": {
16917
+ "code": {
16918
+ "type": "string"
16919
+ },
16920
+ "message": {
16921
+ "type": "string"
16922
+ },
16923
+ "extra": {
16924
+ "type": "object",
16925
+ "additionalProperties": {}
16926
+ }
16927
+ },
16928
+ "required": [
16929
+ "code",
16930
+ "message"
16931
+ ]
16932
+ }
16933
+ },
16934
+ "required": [
16935
+ "error"
16936
+ ]
16937
+ },
16938
+ "examples": {
16939
+ "internalError": {
16940
+ "summary": "Unhandled exception",
16941
+ "value": {
16942
+ "error": {
16943
+ "code": "INTERNAL_ERROR",
16944
+ "message": "internal error"
16945
+ }
16946
+ }
16947
+ }
16948
+ }
16949
+ }
16950
+ }
16951
+ }
16952
+ }
16953
+ }
16954
+ },
16955
+ "/email/domains/{domain}": {
16956
+ "get": {
16957
+ "operationId": "email_domain_get",
16958
+ "tags": [
16959
+ "EmailDomains"
16960
+ ],
16961
+ "summary": "Get one cached custom domain",
16962
+ "description": "### Overview\nReturns the caller organization's cached state for one custom email domain. This is a pure D1 read and never calls the upstream provider.\n\n### When to Use\n- Use this to inspect the latest cached DNS records or verification status for a single domain.\n- This cached view includes ownership, sending readiness, and receiving readiness state for custom-domain alias decisions.\n\n### Constraints\n- Requires a valid Bearer token in the `Authorization` header.\n- The `{domain}` path parameter is normalized and validated server-side before lookup.\n\n### Troubleshooting\n- **400 Bad Request / DOMAIN_INVALID / DOMAIN_RESERVED**: The path hostname is malformed or reserved.\n- **404 Not Found / DOMAIN_NOT_FOUND**: The domain does not exist or belongs to another organization.",
16963
+ "security": [
16964
+ {
16965
+ "bearerAuth": []
16966
+ }
16967
+ ],
16968
+ "x-cli": {
16969
+ "command": "domain show",
16970
+ "positional": [
16971
+ "domain"
16972
+ ],
16973
+ "display": {
16974
+ "shape": "object",
16975
+ "format": {
16976
+ "created_at": "relative-time",
16977
+ "updated_at": "relative-time",
16978
+ "verified_at": "relative-time"
16979
+ },
16980
+ "dataPath": "domain"
16981
+ }
16982
+ },
16983
+ "x-codeSamples": [
16984
+ {
16985
+ "lang": "shell",
16986
+ "label": "curl",
16987
+ "source": "curl https://api.wspc.ai/email/domains/mail.example.com \\\n -H \"Authorization: Bearer $WSPC_API_KEY\""
16988
+ },
16989
+ {
16990
+ "lang": "bash",
16991
+ "label": "wspc CLI",
16992
+ "source": "wspc email domain show mail.example.com"
16993
+ }
16994
+ ],
16995
+ "parameters": [
16996
+ {
16997
+ "schema": {
16998
+ "type": "string",
16999
+ "description": "Custom domain hostname path parameter. URL-encode if your client requires it."
17000
+ },
17001
+ "required": true,
17002
+ "description": "Custom domain hostname path parameter. URL-encode if your client requires it.",
17003
+ "name": "domain",
17004
+ "in": "path"
17005
+ }
17006
+ ],
17007
+ "responses": {
17008
+ "200": {
17009
+ "description": "One custom domain cached for the caller organization, including ownership, sending readiness, and receiving readiness state.",
17010
+ "content": {
17011
+ "application/json": {
17012
+ "schema": {
17013
+ "$ref": "#/components/schemas/EmailDomainObjectResponse"
17014
+ },
17015
+ "examples": {
17016
+ "happyPath": {
17017
+ "summary": "Pending domain cache row",
17018
+ "value": {
17019
+ "domain": {
17020
+ "domain": "mail.example.com",
17021
+ "status": "pending",
17022
+ "sending_status": "pending",
17023
+ "receiving_status": "pending",
17024
+ "records": [
17025
+ {
17026
+ "type": "TXT",
17027
+ "name": "mail.example.com",
17028
+ "value": "pm-domain-verification=abc123",
17029
+ "status": "pending",
17030
+ "ttl": 3600,
17031
+ "purpose": "identity_verification"
17032
+ },
17033
+ {
17034
+ "type": "CNAME",
17035
+ "name": "selector1._domainkey.mail.example.com",
17036
+ "value": "selector1.domainkey.example.net",
17037
+ "status": "pending",
17038
+ "purpose": "dkim"
17039
+ }
17040
+ ],
17041
+ "region": "us-east-1",
17042
+ "created_at": 1780166400000,
17043
+ "updated_at": 1780166400000
17044
+ }
17045
+ }
17046
+ }
17047
+ }
17048
+ }
17049
+ }
17050
+ },
17051
+ "400": {
17052
+ "description": "Malformed or platform-reserved domain hostname.",
17053
+ "content": {
17054
+ "application/json": {
17055
+ "schema": {
17056
+ "type": "object",
17057
+ "properties": {
17058
+ "error": {
17059
+ "type": "object",
17060
+ "properties": {
17061
+ "code": {
17062
+ "type": "string"
17063
+ },
17064
+ "message": {
17065
+ "type": "string"
17066
+ },
17067
+ "extra": {
17068
+ "type": "object",
17069
+ "additionalProperties": {}
17070
+ }
17071
+ },
17072
+ "required": [
17073
+ "code",
17074
+ "message"
17075
+ ]
17076
+ }
17077
+ },
17078
+ "required": [
17079
+ "error"
17080
+ ]
17081
+ }
17082
+ }
17083
+ }
17084
+ },
17085
+ "401": {
17086
+ "description": "Authentication is required but missing or invalid. The Bearer token (API key or OAuth access token) was absent, malformed, or rejected.",
17087
+ "content": {
17088
+ "application/json": {
17089
+ "schema": {
17090
+ "type": "object",
17091
+ "properties": {
17092
+ "error": {
17093
+ "type": "object",
17094
+ "properties": {
17095
+ "code": {
17096
+ "type": "string"
17097
+ },
17098
+ "message": {
17099
+ "type": "string"
17100
+ },
17101
+ "extra": {
17102
+ "type": "object",
17103
+ "additionalProperties": {}
17104
+ }
17105
+ },
17106
+ "required": [
17107
+ "code",
17108
+ "message"
17109
+ ]
17110
+ }
17111
+ },
17112
+ "required": [
17113
+ "error"
17114
+ ]
17115
+ },
17116
+ "examples": {
17117
+ "authRequired": {
17118
+ "summary": "Missing Authorization header",
17119
+ "value": {
17120
+ "error": {
17121
+ "code": "AUTH_REQUIRED",
17122
+ "message": "missing bearer token"
17123
+ }
17124
+ }
17125
+ }
17126
+ }
17127
+ }
17128
+ }
17129
+ },
17130
+ "404": {
17131
+ "description": "The domain was not found for the caller organization.",
17132
+ "content": {
17133
+ "application/json": {
17134
+ "schema": {
17135
+ "type": "object",
17136
+ "properties": {
17137
+ "error": {
17138
+ "type": "object",
17139
+ "properties": {
17140
+ "code": {
17141
+ "type": "string"
17142
+ },
17143
+ "message": {
17144
+ "type": "string"
17145
+ },
17146
+ "extra": {
17147
+ "type": "object",
17148
+ "additionalProperties": {}
17149
+ }
17150
+ },
17151
+ "required": [
17152
+ "code",
17153
+ "message"
17154
+ ]
17155
+ }
17156
+ },
17157
+ "required": [
17158
+ "error"
17159
+ ]
17160
+ }
17161
+ }
17162
+ }
17163
+ },
17164
+ "429": {
17165
+ "description": "Rate limit exceeded. Retry after the duration in `error.extra.retry_after_seconds`. `limit_kind` identifies which bucket was exhausted.",
17166
+ "content": {
17167
+ "application/json": {
17168
+ "schema": {
17169
+ "type": "object",
17170
+ "properties": {
17171
+ "error": {
17172
+ "type": "object",
17173
+ "properties": {
17174
+ "code": {
17175
+ "type": "string"
17176
+ },
17177
+ "message": {
17178
+ "type": "string"
17179
+ },
17180
+ "extra": {
17181
+ "type": "object",
17182
+ "additionalProperties": {}
17183
+ }
17184
+ },
17185
+ "required": [
17186
+ "code",
17187
+ "message"
17188
+ ]
17189
+ }
17190
+ },
17191
+ "required": [
17192
+ "error"
17193
+ ]
17194
+ },
17195
+ "examples": {
17196
+ "rateLimited": {
17197
+ "summary": "Per-key rate limit hit",
17198
+ "value": {
17199
+ "error": {
17200
+ "code": "RATE_LIMITED",
17201
+ "message": "rate limit exceeded",
17202
+ "extra": {
17203
+ "retry_after_seconds": 60,
17204
+ "limit_kind": "authenticated_per_key"
17205
+ }
17206
+ }
17207
+ }
17208
+ }
17209
+ }
17210
+ }
17211
+ }
17212
+ },
17213
+ "500": {
17214
+ "description": "Unhandled server error. The request was well-formed but the service failed unexpectedly. Safe to retry idempotent operations.",
17215
+ "content": {
17216
+ "application/json": {
17217
+ "schema": {
17218
+ "type": "object",
17219
+ "properties": {
17220
+ "error": {
17221
+ "type": "object",
17222
+ "properties": {
17223
+ "code": {
17224
+ "type": "string"
17225
+ },
17226
+ "message": {
17227
+ "type": "string"
17228
+ },
17229
+ "extra": {
17230
+ "type": "object",
17231
+ "additionalProperties": {}
17232
+ }
17233
+ },
17234
+ "required": [
17235
+ "code",
17236
+ "message"
17237
+ ]
17238
+ }
17239
+ },
17240
+ "required": [
17241
+ "error"
17242
+ ]
17243
+ },
17244
+ "examples": {
17245
+ "internalError": {
17246
+ "summary": "Unhandled exception",
17247
+ "value": {
17248
+ "error": {
17249
+ "code": "INTERNAL_ERROR",
17250
+ "message": "internal error"
17251
+ }
17252
+ }
17253
+ }
17254
+ }
17255
+ }
17256
+ }
17257
+ }
17258
+ }
17259
+ }
17260
+ },
17261
+ "/email/messages/{id}": {
17262
+ "get": {
17263
+ "operationId": "email_get",
17264
+ "tags": [
17265
+ "Emails"
17266
+ ],
17267
+ "summary": "Get an inbound email by id",
17268
+ "description": "### Overview\nFetches the metadata and plain-text body of a single inbound email by its unique ID. It also returns metadata for all associated attachments and optionally resolves the rendered HTML content.\n\n### When to Use\n- Use this endpoint to display the complete detail view of an email message.\n- Use it to extract attachment files or read complex HTML layouts.\n\n### Constraints\n- Requires a valid Bearer token in the `Authorization` header.\n- **R2 HTML Read**: The HTML body is stored in Object Storage (R2). To fetch it, explicitly pass `include_html=true` (this incurs an extra R2 read charge; leave unset if only plain text is needed).\n- Returns a 404 error if the email has been soft-deleted, unless `include_deleted=true` is set.\n\n### Troubleshooting\n- **401 Unauthorized**: Missing or expired token.\n- **404 Not Found**: The specified email ID does not exist, belongs to another user, or has been soft-deleted (without `include_deleted=true`).",
17269
+ "security": [
17270
+ {
17271
+ "bearerAuth": []
17272
+ }
17273
+ ],
17274
+ "x-cli": {
17275
+ "command": "email show",
17276
+ "positional": [
17277
+ "id"
17278
+ ],
17279
+ "display": {
17280
+ "shape": "object",
17281
+ "format": {
17282
+ "id": "id-short",
17283
+ "org_id": "id-short",
17284
+ "user_id": "id-short",
17285
+ "received_at": "relative-time",
17286
+ "created_at": "relative-time",
17287
+ "read_at": "relative-time",
17288
+ "deleted_at": "relative-time",
17289
+ "is_read": "bool-badge"
17290
+ },
17291
+ "dataPath": "email"
17292
+ }
17293
+ },
17294
+ "x-codeSamples": [
17295
+ {
17296
+ "lang": "shell",
17297
+ "label": "curl",
17298
+ "source": "curl https://api.wspc.ai/email/messages/eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F \\\n -H \"Authorization: Bearer $WSPC_API_KEY\""
17299
+ },
17300
+ {
17301
+ "lang": "bash",
17302
+ "label": "wspc CLI",
17303
+ "source": "wspc email show eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F"
17304
+ }
17305
+ ],
17306
+ "parameters": [
17307
+ {
17308
+ "schema": {
17309
+ "type": "string"
16102
17310
  },
16103
17311
  "required": true,
16104
17312
  "name": "id",
@@ -17682,16 +18890,271 @@
17682
18890
  "message"
17683
18891
  ]
17684
18892
  }
17685
- },
17686
- "required": [
17687
- "error"
17688
- ]
18893
+ },
18894
+ "required": [
18895
+ "error"
18896
+ ]
18897
+ }
18898
+ }
18899
+ }
18900
+ },
18901
+ "429": {
18902
+ "description": "Either restoring would exceed the per-user active-alias limit (`ALIAS_LIMIT_EXCEEDED`) or the write rate limit was hit (`RATE_LIMITED`).",
18903
+ "content": {
18904
+ "application/json": {
18905
+ "schema": {
18906
+ "type": "object",
18907
+ "properties": {
18908
+ "error": {
18909
+ "type": "object",
18910
+ "properties": {
18911
+ "code": {
18912
+ "type": "string"
18913
+ },
18914
+ "message": {
18915
+ "type": "string"
18916
+ },
18917
+ "extra": {
18918
+ "type": "object",
18919
+ "additionalProperties": {}
18920
+ }
18921
+ },
18922
+ "required": [
18923
+ "code",
18924
+ "message"
18925
+ ]
18926
+ }
18927
+ },
18928
+ "required": [
18929
+ "error"
18930
+ ]
18931
+ }
18932
+ }
18933
+ }
18934
+ },
18935
+ "500": {
18936
+ "description": "Unhandled server error. The request was well-formed but the service failed unexpectedly. Safe to retry idempotent operations.",
18937
+ "content": {
18938
+ "application/json": {
18939
+ "schema": {
18940
+ "type": "object",
18941
+ "properties": {
18942
+ "error": {
18943
+ "type": "object",
18944
+ "properties": {
18945
+ "code": {
18946
+ "type": "string"
18947
+ },
18948
+ "message": {
18949
+ "type": "string"
18950
+ },
18951
+ "extra": {
18952
+ "type": "object",
18953
+ "additionalProperties": {}
18954
+ }
18955
+ },
18956
+ "required": [
18957
+ "code",
18958
+ "message"
18959
+ ]
18960
+ }
18961
+ },
18962
+ "required": [
18963
+ "error"
18964
+ ]
18965
+ },
18966
+ "examples": {
18967
+ "internalError": {
18968
+ "summary": "Unhandled exception",
18969
+ "value": {
18970
+ "error": {
18971
+ "code": "INTERNAL_ERROR",
18972
+ "message": "internal error"
18973
+ }
18974
+ }
18975
+ }
18976
+ }
18977
+ }
18978
+ }
18979
+ }
18980
+ }
18981
+ }
18982
+ },
18983
+ "/email/messages/restore": {
18984
+ "post": {
18985
+ "operationId": "email_restore",
18986
+ "tags": [
18987
+ "Emails"
18988
+ ],
18989
+ "summary": "Restore soft-deleted inbound emails",
18990
+ "description": "### Overview\nRestores a batch of soft-deleted inbound emails from the trash, making them reappear in standard inbox lists.\n\n### When to Use\n- Use this endpoint to recover email messages that were trashed by mistake.\n\n### Constraints\n- Requires a valid Bearer token in the `Authorization` header.\n- Accepts 1 to 100 email IDs. Already-active IDs are silently ignored.\n\n### Troubleshooting\n- **401 Unauthorized**: Invalid token.\n- **400 Bad Request**: Malformed request or batch limit exceeded.",
18991
+ "security": [
18992
+ {
18993
+ "bearerAuth": []
18994
+ }
18995
+ ],
18996
+ "x-cli": {
18997
+ "command": "_internal",
18998
+ "hidden": true
18999
+ },
19000
+ "x-codeSamples": [
19001
+ {
19002
+ "lang": "shell",
19003
+ "label": "curl",
19004
+ "source": "curl -X POST https://api.wspc.ai/email/messages/restore \\\n -H \"Authorization: Bearer $WSPC_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"ids\":[\"eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F\"]}'"
19005
+ }
19006
+ ],
19007
+ "requestBody": {
19008
+ "required": true,
19009
+ "content": {
19010
+ "application/json": {
19011
+ "schema": {
19012
+ "$ref": "#/components/schemas/BatchIdsBody"
19013
+ },
19014
+ "examples": {
19015
+ "minimal": {
19016
+ "summary": "Single id",
19017
+ "value": {
19018
+ "ids": [
19019
+ "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F"
19020
+ ]
19021
+ }
19022
+ },
19023
+ "full": {
19024
+ "summary": "Multiple ids (up to 100 per call)",
19025
+ "value": {
19026
+ "ids": [
19027
+ "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F",
19028
+ "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3G",
19029
+ "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3H"
19030
+ ]
19031
+ }
19032
+ }
19033
+ }
19034
+ }
19035
+ }
19036
+ },
19037
+ "responses": {
19038
+ "200": {
19039
+ "description": "Bulk restore result. `restored` counts state changes only.",
19040
+ "content": {
19041
+ "application/json": {
19042
+ "schema": {
19043
+ "$ref": "#/components/schemas/RestoreBatchResponse"
19044
+ },
19045
+ "examples": {
19046
+ "happyPath": {
19047
+ "summary": "All ids matched and changed state",
19048
+ "value": {
19049
+ "restored": 2,
19050
+ "not_found": []
19051
+ }
19052
+ },
19053
+ "partial": {
19054
+ "summary": "Some ids unknown to this user",
19055
+ "value": {
19056
+ "restored": 1,
19057
+ "not_found": [
19058
+ "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3X"
19059
+ ]
19060
+ }
19061
+ }
19062
+ }
19063
+ }
19064
+ }
19065
+ },
19066
+ "400": {
19067
+ "description": "Request validation failed. The body, query, or path parameters did not match the operation's schema.",
19068
+ "content": {
19069
+ "application/json": {
19070
+ "schema": {
19071
+ "type": "object",
19072
+ "properties": {
19073
+ "error": {
19074
+ "type": "object",
19075
+ "properties": {
19076
+ "code": {
19077
+ "type": "string"
19078
+ },
19079
+ "message": {
19080
+ "type": "string"
19081
+ },
19082
+ "extra": {
19083
+ "type": "object",
19084
+ "additionalProperties": {}
19085
+ }
19086
+ },
19087
+ "required": [
19088
+ "code",
19089
+ "message"
19090
+ ]
19091
+ }
19092
+ },
19093
+ "required": [
19094
+ "error"
19095
+ ]
19096
+ },
19097
+ "examples": {
19098
+ "validationError": {
19099
+ "summary": "Body failed schema validation",
19100
+ "value": {
19101
+ "error": {
19102
+ "code": "VALIDATION_ERROR",
19103
+ "message": "title must not be empty"
19104
+ }
19105
+ }
19106
+ }
19107
+ }
19108
+ }
19109
+ }
19110
+ },
19111
+ "401": {
19112
+ "description": "Authentication is required but missing or invalid. The Bearer token (API key or OAuth access token) was absent, malformed, or rejected.",
19113
+ "content": {
19114
+ "application/json": {
19115
+ "schema": {
19116
+ "type": "object",
19117
+ "properties": {
19118
+ "error": {
19119
+ "type": "object",
19120
+ "properties": {
19121
+ "code": {
19122
+ "type": "string"
19123
+ },
19124
+ "message": {
19125
+ "type": "string"
19126
+ },
19127
+ "extra": {
19128
+ "type": "object",
19129
+ "additionalProperties": {}
19130
+ }
19131
+ },
19132
+ "required": [
19133
+ "code",
19134
+ "message"
19135
+ ]
19136
+ }
19137
+ },
19138
+ "required": [
19139
+ "error"
19140
+ ]
19141
+ },
19142
+ "examples": {
19143
+ "authRequired": {
19144
+ "summary": "Missing Authorization header",
19145
+ "value": {
19146
+ "error": {
19147
+ "code": "AUTH_REQUIRED",
19148
+ "message": "missing bearer token"
19149
+ }
19150
+ }
19151
+ }
17689
19152
  }
17690
19153
  }
17691
19154
  }
17692
19155
  },
17693
19156
  "429": {
17694
- "description": "Either restoring would exceed the per-user active-alias limit (`ALIAS_LIMIT_EXCEEDED`) or the write rate limit was hit (`RATE_LIMITED`).",
19157
+ "description": "Rate limit exceeded. Retry after the duration in `error.extra.retry_after_seconds`. `limit_kind` identifies which bucket was exhausted.",
17695
19158
  "content": {
17696
19159
  "application/json": {
17697
19160
  "schema": {
@@ -17720,6 +19183,21 @@
17720
19183
  "required": [
17721
19184
  "error"
17722
19185
  ]
19186
+ },
19187
+ "examples": {
19188
+ "rateLimited": {
19189
+ "summary": "Per-key rate limit hit",
19190
+ "value": {
19191
+ "error": {
19192
+ "code": "RATE_LIMITED",
19193
+ "message": "rate limit exceeded",
19194
+ "extra": {
19195
+ "retry_after_seconds": 60,
19196
+ "limit_kind": "authenticated_per_key"
19197
+ }
19198
+ }
19199
+ }
19200
+ }
17723
19201
  }
17724
19202
  }
17725
19203
  }
@@ -17772,28 +19250,33 @@
17772
19250
  }
17773
19251
  }
17774
19252
  },
17775
- "/email/messages/restore": {
19253
+ "/email/messages/send": {
17776
19254
  "post": {
17777
- "operationId": "email_restore",
19255
+ "operationId": "email_send",
17778
19256
  "tags": [
17779
19257
  "Emails"
17780
19258
  ],
17781
- "summary": "Restore soft-deleted inbound emails",
17782
- "description": "### Overview\nRestores a batch of soft-deleted inbound emails from the trash, making them reappear in standard inbox lists.\n\n### When to Use\n- Use this endpoint to recover email messages that were trashed by mistake.\n\n### Constraints\n- Requires a valid Bearer token in the `Authorization` header.\n- Accepts 1 to 100 email IDs. Already-active IDs are silently ignored.\n\n### Troubleshooting\n- **401 Unauthorized**: Invalid token.\n- **400 Bad Request**: Malformed request or batch limit exceeded.",
19259
+ "summary": "Send an outbound email",
19260
+ "description": "### Overview\nSubmits a single outbound email for delivery from one of the caller's active aliases. All details, including attachments (inline base64 blobs or references to existing inbound attachments), are verified before sending. Platform-domain aliases use Cloudflare Email Service; verified custom-domain aliases use pete-mail.\n\n### When to Use\n- Use this endpoint to send new standalone emails or to reply to threaded inbound messages.\n- Use this in automated agent pipelines (like calendar invite generation or notifications) and CLI email send utilities.\n\n### Constraints\n- Requires a valid Bearer token in the `Authorization` header.\n- **Size Limits**: Individual attachments must not exceed 5 MiB, and the total size of all attachments per send must be 25 MiB or less.\n- **Security**: Up to 10 attachments are allowed. Outbound files with dangerous executable extensions (such as `.exe`, `.bat`, `.com`, `.scr`, `.cmd`, `.jar`, `.js`) are strictly blocked.\n- **Daily Quotas**: Sending is protected by per-user (100 sends/day) and per-alias (50 sends/day) daily quotas. Exceeding them triggers `RATE_LIMITED` or `QUOTA_EXCEEDED` errors.\n- **Custom Domains**: Platform-domain aliases use Cloudflare Email Service. Verified custom-domain aliases are routed through pete-mail. Custom domains must have `status = verified` and `sending_status = verified` or the send returns `CUSTOM_DOMAIN_NOT_READY`.\n- **Idempotency**: A stable `idempotency_key` (1-200 characters) must be supplied. Retrying a send with identical content and the same key returns `idempotent_replay: true` without sending duplicates. Reusing the key with changed content returns 409 `IDEMPOTENCY_KEY_REUSED`.\n\n### Troubleshooting\n- **401 Unauthorized**: Active Bearer token is invalid or has expired.\n- **404 Not Found**: The requested `from_alias_email` does not exist or has been soft-deleted, or the referenced `in_reply_to_email_id` is missing or belongs to a different user.\n- **409 Conflict / IDEMPOTENCY_KEY_REUSED**: An identical `idempotency_key` was reused with modified request payload. Use a fresh unique key.\n- **409 Conflict / CUSTOM_DOMAIN_NOT_READY**: The sender uses a custom domain that has not completed outbound sending verification.\n- **429 Too Many Requests / RATE_LIMITED**: The per-user rate limit or daily sending quota has been exceeded. Wait for quota reset.\n- **502 Bad Gateway**: The upstream outbound provider failed or rejected the message. The outbound row is persisted with `status: failed` along with provider-returned logs.",
17783
19261
  "security": [
17784
19262
  {
17785
19263
  "bearerAuth": []
17786
19264
  }
17787
19265
  ],
17788
19266
  "x-cli": {
17789
- "command": "_internal",
19267
+ "command": "_handwritten",
17790
19268
  "hidden": true
17791
19269
  },
17792
19270
  "x-codeSamples": [
17793
19271
  {
17794
19272
  "lang": "shell",
17795
19273
  "label": "curl",
17796
- "source": "curl -X POST https://api.wspc.ai/email/messages/restore \\\n -H \"Authorization: Bearer $WSPC_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"ids\":[\"eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F\"]}'"
19274
+ "source": "curl -X POST https://api.wspc.ai/email/messages/send \\\n -H \"Authorization: Bearer $WSPC_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"from_alias_email\":\"alice-shop@wspc.app\",\"to\":[\"alice@example.com\"],\"subject\":\"Welcome\",\"text\":\"Hi Alice\",\"idempotency_key\":\"retry-20260601-001\"}'"
19275
+ },
19276
+ {
19277
+ "lang": "bash",
19278
+ "label": "wspc CLI",
19279
+ "source": "wspc email send \\\n --from alice-shop@wspc.app \\\n --to alice@example.com \\\n --subject \"Welcome to WSPC\" \\\n --text \"Hi Alice — welcome aboard.\" \\\n --attach ./report.pdf \\\n --idempotency-key retry-20260601-001"
17797
19280
  }
17798
19281
  ],
17799
19282
  "requestBody": {
@@ -17801,25 +19284,41 @@
17801
19284
  "content": {
17802
19285
  "application/json": {
17803
19286
  "schema": {
17804
- "$ref": "#/components/schemas/BatchIdsBody"
19287
+ "$ref": "#/components/schemas/SendEmailBody"
17805
19288
  },
17806
19289
  "examples": {
17807
19290
  "minimal": {
17808
- "summary": "Single id",
19291
+ "summary": "Fresh send — required fields only",
17809
19292
  "value": {
17810
- "ids": [
17811
- "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F"
17812
- ]
19293
+ "from_alias_email": "alice-shop@wspc.app",
19294
+ "to": [
19295
+ "alice@example.com"
19296
+ ],
19297
+ "subject": "Welcome to WSPC",
19298
+ "text": "Hi Alice — welcome aboard.",
19299
+ "idempotency_key": "retry-20260601-001"
19300
+ }
19301
+ },
19302
+ "reply": {
19303
+ "summary": "Threaded reply — to/subject derived from the inbound message",
19304
+ "value": {
19305
+ "from_alias_email": "alice-shop@wspc.app",
19306
+ "in_reply_to_email_id": "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F",
19307
+ "text": "Thanks — replying inline.",
19308
+ "idempotency_key": "reply-20260601-001"
17813
19309
  }
17814
19310
  },
17815
19311
  "full": {
17816
- "summary": "Multiple ids (up to 100 per call)",
19312
+ "summary": "Multi-recipient with explicit subject",
17817
19313
  "value": {
17818
- "ids": [
17819
- "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F",
17820
- "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3G",
17821
- "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3H"
17822
- ]
19314
+ "from_alias_email": "alice-shop@wspc.app",
19315
+ "to": [
19316
+ "alice@example.com",
19317
+ "bob@example.com"
19318
+ ],
19319
+ "subject": "Lunch with Alice",
19320
+ "text": "Confirming lunch — see you at the lobby at 12:30.",
19321
+ "idempotency_key": "lunch-confirm-20260601"
17823
19322
  }
17824
19323
  }
17825
19324
  }
@@ -17828,35 +19327,195 @@
17828
19327
  },
17829
19328
  "responses": {
17830
19329
  "200": {
17831
- "description": "Bulk restore result. `restored` counts state changes only.",
19330
+ "description": "The send was accepted. Response `email.id` uses `out_<ULID>` for new outbound rows; legacy UUID ids remain accepted. Inspect `idempotent_replay`: `false` means the provider was invoked on this request, `true` means an earlier identical send was replayed.",
17832
19331
  "content": {
17833
19332
  "application/json": {
17834
19333
  "schema": {
17835
- "$ref": "#/components/schemas/RestoreBatchResponse"
19334
+ "$ref": "#/components/schemas/SendEmailResponse"
17836
19335
  },
17837
19336
  "examples": {
17838
19337
  "happyPath": {
17839
- "summary": "All ids matched and changed state",
19338
+ "summary": "Fresh send succeeded",
17840
19339
  "value": {
17841
- "restored": 2,
17842
- "not_found": []
19340
+ "email": {
19341
+ "id": "out_01HW3K4N9V5G6Z8C2Q7B1Y0M3F",
19342
+ "org_id": "org_01HW3K4N9V5G6Z8C2Q7B1Y0M3F",
19343
+ "user_id": "usr_01HW3K4N9V5G6Z8C2Q7B1Y0M3F",
19344
+ "from_alias_email": "alice-shop@wspc.app",
19345
+ "from_addr": "alice-shop@wspc.app",
19346
+ "to": [
19347
+ "alice@example.com"
19348
+ ],
19349
+ "subject": "Welcome to WSPC",
19350
+ "text_body": "Hi Alice — welcome aboard.",
19351
+ "message_id": "<01HW3K4N9V5G6Z8C2Q7B1Y0M3G@wspc.app>",
19352
+ "provider": "cloudflare-email-service",
19353
+ "request_hash": "sha256:abc...",
19354
+ "status": "sent",
19355
+ "idempotency_key": "retry-20260601-001",
19356
+ "submitted_at": 1748736000500,
19357
+ "created_at": 1748736000000,
19358
+ "updated_at": 1748736000500
19359
+ },
19360
+ "idempotent_replay": false
17843
19361
  }
17844
19362
  },
17845
- "partial": {
17846
- "summary": "Some ids unknown to this user",
19363
+ "replay": {
19364
+ "summary": "Idempotent replay provider not re-invoked",
17847
19365
  "value": {
17848
- "restored": 1,
17849
- "not_found": [
17850
- "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3X"
19366
+ "email": {
19367
+ "id": "out_01HW3K4N9V5G6Z8C2Q7B1Y0M3F",
19368
+ "org_id": "org_01HW3K4N9V5G6Z8C2Q7B1Y0M3F",
19369
+ "user_id": "usr_01HW3K4N9V5G6Z8C2Q7B1Y0M3F",
19370
+ "from_alias_email": "alice-shop@wspc.app",
19371
+ "from_addr": "alice-shop@wspc.app",
19372
+ "to": [
19373
+ "alice@example.com"
19374
+ ],
19375
+ "subject": "Welcome to WSPC",
19376
+ "text_body": "Hi Alice — welcome aboard.",
19377
+ "message_id": "<01HW3K4N9V5G6Z8C2Q7B1Y0M3G@wspc.app>",
19378
+ "provider": "cloudflare-email-service",
19379
+ "request_hash": "sha256:abc...",
19380
+ "status": "sent",
19381
+ "idempotency_key": "retry-20260601-001",
19382
+ "submitted_at": 1748736000500,
19383
+ "created_at": 1748736000000,
19384
+ "updated_at": 1748736000500
19385
+ },
19386
+ "idempotent_replay": true
19387
+ }
19388
+ }
19389
+ }
19390
+ }
19391
+ }
19392
+ },
19393
+ "400": {
19394
+ "description": "Request validation failed. The body, query, or path parameters did not match the operation's schema.",
19395
+ "content": {
19396
+ "application/json": {
19397
+ "schema": {
19398
+ "type": "object",
19399
+ "properties": {
19400
+ "error": {
19401
+ "type": "object",
19402
+ "properties": {
19403
+ "code": {
19404
+ "type": "string"
19405
+ },
19406
+ "message": {
19407
+ "type": "string"
19408
+ },
19409
+ "extra": {
19410
+ "type": "object",
19411
+ "additionalProperties": {}
19412
+ }
19413
+ },
19414
+ "required": [
19415
+ "code",
19416
+ "message"
19417
+ ]
19418
+ }
19419
+ },
19420
+ "required": [
19421
+ "error"
19422
+ ]
19423
+ },
19424
+ "examples": {
19425
+ "validationError": {
19426
+ "summary": "Body failed schema validation",
19427
+ "value": {
19428
+ "error": {
19429
+ "code": "VALIDATION_ERROR",
19430
+ "message": "title must not be empty"
19431
+ }
19432
+ }
19433
+ }
19434
+ }
19435
+ }
19436
+ }
19437
+ },
19438
+ "401": {
19439
+ "description": "Authentication is required but missing or invalid. The Bearer token (API key or OAuth access token) was absent, malformed, or rejected.",
19440
+ "content": {
19441
+ "application/json": {
19442
+ "schema": {
19443
+ "type": "object",
19444
+ "properties": {
19445
+ "error": {
19446
+ "type": "object",
19447
+ "properties": {
19448
+ "code": {
19449
+ "type": "string"
19450
+ },
19451
+ "message": {
19452
+ "type": "string"
19453
+ },
19454
+ "extra": {
19455
+ "type": "object",
19456
+ "additionalProperties": {}
19457
+ }
19458
+ },
19459
+ "required": [
19460
+ "code",
19461
+ "message"
19462
+ ]
19463
+ }
19464
+ },
19465
+ "required": [
19466
+ "error"
19467
+ ]
19468
+ },
19469
+ "examples": {
19470
+ "authRequired": {
19471
+ "summary": "Missing Authorization header",
19472
+ "value": {
19473
+ "error": {
19474
+ "code": "AUTH_REQUIRED",
19475
+ "message": "missing bearer token"
19476
+ }
19477
+ }
19478
+ }
19479
+ }
19480
+ }
19481
+ }
19482
+ },
19483
+ "404": {
19484
+ "description": "`from_alias_email` is unknown / soft-deleted, or `in_reply_to_email_id` is not visible to the caller.",
19485
+ "content": {
19486
+ "application/json": {
19487
+ "schema": {
19488
+ "type": "object",
19489
+ "properties": {
19490
+ "error": {
19491
+ "type": "object",
19492
+ "properties": {
19493
+ "code": {
19494
+ "type": "string"
19495
+ },
19496
+ "message": {
19497
+ "type": "string"
19498
+ },
19499
+ "extra": {
19500
+ "type": "object",
19501
+ "additionalProperties": {}
19502
+ }
19503
+ },
19504
+ "required": [
19505
+ "code",
19506
+ "message"
17851
19507
  ]
17852
19508
  }
17853
- }
19509
+ },
19510
+ "required": [
19511
+ "error"
19512
+ ]
17854
19513
  }
17855
19514
  }
17856
19515
  }
17857
19516
  },
17858
- "400": {
17859
- "description": "Request validation failed. The body, query, or path parameters did not match the operation's schema.",
19517
+ "409": {
19518
+ "description": "Either an earlier send under this `idempotency_key` had different content, or the sender custom domain has not completed outbound sending verification.",
17860
19519
  "content": {
17861
19520
  "application/json": {
17862
19521
  "schema": {
@@ -17885,23 +19544,12 @@
17885
19544
  "required": [
17886
19545
  "error"
17887
19546
  ]
17888
- },
17889
- "examples": {
17890
- "validationError": {
17891
- "summary": "Body failed schema validation",
17892
- "value": {
17893
- "error": {
17894
- "code": "VALIDATION_ERROR",
17895
- "message": "title must not be empty"
17896
- }
17897
- }
17898
- }
17899
19547
  }
17900
19548
  }
17901
19549
  }
17902
19550
  },
17903
- "401": {
17904
- "description": "Authentication is required but missing or invalid. The Bearer token (API key or OAuth access token) was absent, malformed, or rejected.",
19551
+ "429": {
19552
+ "description": "Either the middleware send rate limit (`RATE_LIMITED`), the per-user daily cap of 100 sends (`RATE_LIMITED`), or the per-alias daily cap of 50 sends (`QUOTA_EXCEEDED`).",
17905
19553
  "content": {
17906
19554
  "application/json": {
17907
19555
  "schema": {
@@ -17930,23 +19578,12 @@
17930
19578
  "required": [
17931
19579
  "error"
17932
19580
  ]
17933
- },
17934
- "examples": {
17935
- "authRequired": {
17936
- "summary": "Missing Authorization header",
17937
- "value": {
17938
- "error": {
17939
- "code": "AUTH_REQUIRED",
17940
- "message": "missing bearer token"
17941
- }
17942
- }
17943
- }
17944
19581
  }
17945
19582
  }
17946
19583
  }
17947
19584
  },
17948
- "429": {
17949
- "description": "Rate limit exceeded. Retry after the duration in `error.extra.retry_after_seconds`. `limit_kind` identifies which bucket was exhausted.",
19585
+ "500": {
19586
+ "description": "Unhandled server error. The request was well-formed but the service failed unexpectedly. Safe to retry idempotent operations.",
17950
19587
  "content": {
17951
19588
  "application/json": {
17952
19589
  "schema": {
@@ -17977,16 +19614,12 @@
17977
19614
  ]
17978
19615
  },
17979
19616
  "examples": {
17980
- "rateLimited": {
17981
- "summary": "Per-key rate limit hit",
19617
+ "internalError": {
19618
+ "summary": "Unhandled exception",
17982
19619
  "value": {
17983
19620
  "error": {
17984
- "code": "RATE_LIMITED",
17985
- "message": "rate limit exceeded",
17986
- "extra": {
17987
- "retry_after_seconds": 60,
17988
- "limit_kind": "authenticated_per_key"
17989
- }
19621
+ "code": "INTERNAL_ERROR",
19622
+ "message": "internal error"
17990
19623
  }
17991
19624
  }
17992
19625
  }
@@ -17994,8 +19627,8 @@
17994
19627
  }
17995
19628
  }
17996
19629
  },
17997
- "500": {
17998
- "description": "Unhandled server error. The request was well-formed but the service failed unexpectedly. Safe to retry idempotent operations.",
19630
+ "502": {
19631
+ "description": "The upstream email provider rejected the message. The row is persisted with `status: failed` and `error_code` / `error_message` set.",
17999
19632
  "content": {
18000
19633
  "application/json": {
18001
19634
  "schema": {
@@ -18024,17 +19657,6 @@
18024
19657
  "required": [
18025
19658
  "error"
18026
19659
  ]
18027
- },
18028
- "examples": {
18029
- "internalError": {
18030
- "summary": "Unhandled exception",
18031
- "value": {
18032
- "error": {
18033
- "code": "INTERNAL_ERROR",
18034
- "message": "internal error"
18035
- }
18036
- }
18037
- }
18038
19660
  }
18039
19661
  }
18040
19662
  }
@@ -18042,140 +19664,128 @@
18042
19664
  }
18043
19665
  }
18044
19666
  },
18045
- "/email/messages/send": {
19667
+ "/email/domains/{domain}/verify": {
18046
19668
  "post": {
18047
- "operationId": "email_send",
19669
+ "operationId": "email_domain_verify",
18048
19670
  "tags": [
18049
- "Emails"
19671
+ "EmailDomains"
18050
19672
  ],
18051
- "summary": "Send an outbound email",
18052
- "description": "### Overview\nSubmits a single outbound email for delivery from one of the caller's active aliases. All details, including attachments (inline base64 blobs or references to existing inbound attachments), are verified before sending. Platform-domain aliases use Cloudflare Email Service; verified custom-domain aliases use pete-mail.\n\n### When to Use\n- Use this endpoint to send new standalone emails or to reply to threaded inbound messages.\n- Use this in automated agent pipelines (like calendar invite generation or notifications) and CLI email send utilities.\n\n### Constraints\n- Requires a valid Bearer token in the `Authorization` header.\n- **Size Limits**: Individual attachments must not exceed 5 MiB, and the total size of all attachments per send must be 25 MiB or less.\n- **Security**: Up to 10 attachments are allowed. Outbound files with dangerous executable extensions (such as `.exe`, `.bat`, `.com`, `.scr`, `.cmd`, `.jar`, `.js`) are strictly blocked.\n- **Daily Quotas**: Sending is protected by per-user (100 sends/day) and per-alias (50 sends/day) daily quotas. Exceeding them triggers `RATE_LIMITED` or `QUOTA_EXCEEDED` errors.\n- **Custom Domains**: Platform-domain aliases use Cloudflare Email Service. Verified custom-domain aliases are routed through pete-mail. Custom domains must have `status = verified` and `sending_status = verified` or the send returns `CUSTOM_DOMAIN_NOT_READY`.\n- **Idempotency**: A stable `idempotency_key` (1-200 characters) must be supplied. Retrying a send with identical content and the same key returns `idempotent_replay: true` without sending duplicates. Reusing the key with changed content returns 409 `IDEMPOTENCY_KEY_REUSED`.\n\n### Troubleshooting\n- **401 Unauthorized**: Active Bearer token is invalid or has expired.\n- **404 Not Found**: The requested `from_alias_email` does not exist or has been soft-deleted, or the referenced `in_reply_to_email_id` is missing or belongs to a different user.\n- **409 Conflict / IDEMPOTENCY_KEY_REUSED**: An identical `idempotency_key` was reused with modified request payload. Use a fresh unique key.\n- **409 Conflict / CUSTOM_DOMAIN_NOT_READY**: The sender uses a custom domain that has not completed outbound sending verification.\n- **429 Too Many Requests / RATE_LIMITED**: The per-user rate limit or daily sending quota has been exceeded. Wait for quota reset.\n- **502 Bad Gateway**: The upstream outbound provider failed or rejected the message. The outbound row is persisted with `status: failed` along with provider-returned logs.",
19673
+ "summary": "Verify a custom domain with the provider",
19674
+ "description": "### Overview\nTriggers an upstream provider verification attempt for one custom email domain, refreshes the cached DNS records/status in D1, and returns the updated row.\nThis route refreshes DNS registration and verification state. Custom-domain aliases require `status`, `sending_status`, and `receiving_status` to all be `verified`.\n\n### When to Use\n- Use this after publishing the required DNS records, or whenever you want to refresh cached provider state explicitly.\n- If the provider verify call returns incomplete DNS records, the worker performs a follow-up provider read before responding.\n\n### Constraints\n- Requires a valid Bearer token in the `Authorization` header.\n- This route requires custom domain provider credentials in production because it performs live provider calls.\n- Verification is asynchronous provider work; a successful response may still report `status: pending`.\n- `status: verified` plus `sending_status: verified` enables custom-domain outbound send for active aliases; `receiving_status: verified` is also required before new custom-domain aliases can be created.\n\n### Troubleshooting\n- **400 Bad Request / DOMAIN_INVALID / DOMAIN_RESERVED**: The path hostname is malformed or reserved.\n- **404 Not Found / DOMAIN_NOT_FOUND**: The domain does not exist or belongs to another organization.\n- **502 Bad Gateway / DOMAIN_PROVIDER_ERROR**: Provider verification failed, timed out, or credentials are missing.",
18053
19675
  "security": [
18054
19676
  {
18055
19677
  "bearerAuth": []
18056
19678
  }
18057
19679
  ],
18058
19680
  "x-cli": {
18059
- "command": "_handwritten",
18060
- "hidden": true
19681
+ "command": "domain verify",
19682
+ "positional": [
19683
+ "domain"
19684
+ ],
19685
+ "display": {
19686
+ "shape": "object",
19687
+ "format": {
19688
+ "created_at": "relative-time",
19689
+ "updated_at": "relative-time",
19690
+ "verified_at": "relative-time"
19691
+ },
19692
+ "dataPath": "domain"
19693
+ }
18061
19694
  },
18062
19695
  "x-codeSamples": [
18063
19696
  {
18064
19697
  "lang": "shell",
18065
19698
  "label": "curl",
18066
- "source": "curl -X POST https://api.wspc.ai/email/messages/send \\\n -H \"Authorization: Bearer $WSPC_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"from_alias_email\":\"alice-shop@wspc.app\",\"to\":[\"alice@example.com\"],\"subject\":\"Welcome\",\"text\":\"Hi Alice\",\"idempotency_key\":\"retry-20260601-001\"}'"
19699
+ "source": "curl -X POST https://api.wspc.ai/email/domains/mail.example.com/verify \\\n -H \"Authorization: Bearer $WSPC_API_KEY\""
18067
19700
  },
18068
19701
  {
18069
19702
  "lang": "bash",
18070
19703
  "label": "wspc CLI",
18071
- "source": "wspc email send \\\n --from alice-shop@wspc.app \\\n --to alice@example.com \\\n --subject \"Welcome to WSPC\" \\\n --text \"Hi Alice — welcome aboard.\" \\\n --attach ./report.pdf \\\n --idempotency-key retry-20260601-001"
19704
+ "source": "wspc email domain verify mail.example.com"
18072
19705
  }
18073
19706
  ],
18074
- "requestBody": {
18075
- "required": true,
18076
- "content": {
18077
- "application/json": {
18078
- "schema": {
18079
- "$ref": "#/components/schemas/SendEmailBody"
18080
- },
18081
- "examples": {
18082
- "minimal": {
18083
- "summary": "Fresh send — required fields only",
18084
- "value": {
18085
- "from_alias_email": "alice-shop@wspc.app",
18086
- "to": [
18087
- "alice@example.com"
18088
- ],
18089
- "subject": "Welcome to WSPC",
18090
- "text": "Hi Alice — welcome aboard.",
18091
- "idempotency_key": "retry-20260601-001"
18092
- }
18093
- },
18094
- "reply": {
18095
- "summary": "Threaded reply — to/subject derived from the inbound message",
18096
- "value": {
18097
- "from_alias_email": "alice-shop@wspc.app",
18098
- "in_reply_to_email_id": "eml_01HW3K4N9V5G6Z8C2Q7B1Y0M3F",
18099
- "text": "Thanks — replying inline.",
18100
- "idempotency_key": "reply-20260601-001"
18101
- }
18102
- },
18103
- "full": {
18104
- "summary": "Multi-recipient with explicit subject",
18105
- "value": {
18106
- "from_alias_email": "alice-shop@wspc.app",
18107
- "to": [
18108
- "alice@example.com",
18109
- "bob@example.com"
18110
- ],
18111
- "subject": "Lunch with Alice",
18112
- "text": "Confirming lunch — see you at the lobby at 12:30.",
18113
- "idempotency_key": "lunch-confirm-20260601"
18114
- }
18115
- }
18116
- }
18117
- }
19707
+ "parameters": [
19708
+ {
19709
+ "schema": {
19710
+ "type": "string",
19711
+ "description": "Custom domain hostname path parameter. URL-encode if your client requires it."
19712
+ },
19713
+ "required": true,
19714
+ "description": "Custom domain hostname path parameter. URL-encode if your client requires it.",
19715
+ "name": "domain",
19716
+ "in": "path"
18118
19717
  }
18119
- },
19718
+ ],
18120
19719
  "responses": {
18121
19720
  "200": {
18122
- "description": "The send was accepted. Response `email.id` uses `out_<ULID>` for new outbound rows; legacy UUID ids remain accepted. Inspect `idempotent_replay`: `false` means the provider was invoked on this request, `true` means an earlier identical send was replayed.",
19721
+ "description": "Updated cached provider state after a verification attempt, including ownership, sending readiness, and receiving readiness.",
18123
19722
  "content": {
18124
19723
  "application/json": {
18125
19724
  "schema": {
18126
- "$ref": "#/components/schemas/SendEmailResponse"
19725
+ "$ref": "#/components/schemas/EmailDomainObjectResponse"
18127
19726
  },
18128
19727
  "examples": {
18129
19728
  "happyPath": {
18130
- "summary": "Fresh send succeeded",
18131
- "value": {
18132
- "email": {
18133
- "id": "out_01HW3K4N9V5G6Z8C2Q7B1Y0M3F",
18134
- "org_id": "org_01HW3K4N9V5G6Z8C2Q7B1Y0M3F",
18135
- "user_id": "usr_01HW3K4N9V5G6Z8C2Q7B1Y0M3F",
18136
- "from_alias_email": "alice-shop@wspc.app",
18137
- "from_addr": "alice-shop@wspc.app",
18138
- "to": [
18139
- "alice@example.com"
19729
+ "summary": "Domain ownership and sending verified; receiving is not ready yet",
19730
+ "value": {
19731
+ "domain": {
19732
+ "domain": "mail.example.com",
19733
+ "status": "verified",
19734
+ "sending_status": "verified",
19735
+ "receiving_status": "disabled",
19736
+ "records": [
19737
+ {
19738
+ "type": "TXT",
19739
+ "name": "mail.example.com",
19740
+ "value": "pm-domain-verification=abc123",
19741
+ "status": "verified",
19742
+ "ttl": 3600,
19743
+ "purpose": "identity_verification"
19744
+ },
19745
+ {
19746
+ "type": "CNAME",
19747
+ "name": "selector1._domainkey.mail.example.com",
19748
+ "value": "selector1.domainkey.example.net",
19749
+ "status": "verified",
19750
+ "purpose": "dkim"
19751
+ }
18140
19752
  ],
18141
- "subject": "Welcome to WSPC",
18142
- "text_body": "Hi Alice — welcome aboard.",
18143
- "message_id": "<01HW3K4N9V5G6Z8C2Q7B1Y0M3G@wspc.app>",
18144
- "provider": "cloudflare-email-service",
18145
- "request_hash": "sha256:abc...",
18146
- "status": "sent",
18147
- "idempotency_key": "retry-20260601-001",
18148
- "submitted_at": 1748736000500,
18149
- "created_at": 1748736000000,
18150
- "updated_at": 1748736000500
18151
- },
18152
- "idempotent_replay": false
18153
- }
18154
- },
18155
- "replay": {
18156
- "summary": "Idempotent replay — provider not re-invoked",
18157
- "value": {
18158
- "email": {
18159
- "id": "out_01HW3K4N9V5G6Z8C2Q7B1Y0M3F",
18160
- "org_id": "org_01HW3K4N9V5G6Z8C2Q7B1Y0M3F",
18161
- "user_id": "usr_01HW3K4N9V5G6Z8C2Q7B1Y0M3F",
18162
- "from_alias_email": "alice-shop@wspc.app",
18163
- "from_addr": "alice-shop@wspc.app",
18164
- "to": [
18165
- "alice@example.com"
19753
+ "region": "us-east-1",
19754
+ "created_at": 1780166400000,
19755
+ "updated_at": 1780167000000,
19756
+ "verified_at": 1780167000000
19757
+ }
19758
+ }
19759
+ },
19760
+ "stillPending": {
19761
+ "summary": "Provider accepted verify request but DNS is not ready yet",
19762
+ "value": {
19763
+ "domain": {
19764
+ "domain": "mail.example.com",
19765
+ "status": "pending",
19766
+ "sending_status": "pending",
19767
+ "receiving_status": "pending",
19768
+ "records": [
19769
+ {
19770
+ "type": "TXT",
19771
+ "name": "mail.example.com",
19772
+ "value": "pm-domain-verification=abc123",
19773
+ "status": "pending",
19774
+ "ttl": 3600,
19775
+ "purpose": "identity_verification"
19776
+ },
19777
+ {
19778
+ "type": "CNAME",
19779
+ "name": "selector1._domainkey.mail.example.com",
19780
+ "value": "selector1.domainkey.example.net",
19781
+ "status": "pending",
19782
+ "purpose": "dkim"
19783
+ }
18166
19784
  ],
18167
- "subject": "Welcome to WSPC",
18168
- "text_body": "Hi Alice — welcome aboard.",
18169
- "message_id": "<01HW3K4N9V5G6Z8C2Q7B1Y0M3G@wspc.app>",
18170
- "provider": "cloudflare-email-service",
18171
- "request_hash": "sha256:abc...",
18172
- "status": "sent",
18173
- "idempotency_key": "retry-20260601-001",
18174
- "submitted_at": 1748736000500,
18175
- "created_at": 1748736000000,
18176
- "updated_at": 1748736000500
18177
- },
18178
- "idempotent_replay": true
19785
+ "region": "us-east-1",
19786
+ "created_at": 1780166400000,
19787
+ "updated_at": 1780166400000
19788
+ }
18179
19789
  }
18180
19790
  }
18181
19791
  }
@@ -18183,7 +19793,7 @@
18183
19793
  }
18184
19794
  },
18185
19795
  "400": {
18186
- "description": "Request validation failed. The body, query, or path parameters did not match the operation's schema.",
19796
+ "description": "Malformed or platform-reserved domain hostname.",
18187
19797
  "content": {
18188
19798
  "application/json": {
18189
19799
  "schema": {
@@ -18212,17 +19822,6 @@
18212
19822
  "required": [
18213
19823
  "error"
18214
19824
  ]
18215
- },
18216
- "examples": {
18217
- "validationError": {
18218
- "summary": "Body failed schema validation",
18219
- "value": {
18220
- "error": {
18221
- "code": "VALIDATION_ERROR",
18222
- "message": "title must not be empty"
18223
- }
18224
- }
18225
- }
18226
19825
  }
18227
19826
  }
18228
19827
  }
@@ -18273,7 +19872,7 @@
18273
19872
  }
18274
19873
  },
18275
19874
  "404": {
18276
- "description": "`from_alias_email` is unknown / soft-deleted, or `in_reply_to_email_id` is not visible to the caller.",
19875
+ "description": "The domain was not found for the caller organization.",
18277
19876
  "content": {
18278
19877
  "application/json": {
18279
19878
  "schema": {
@@ -18306,8 +19905,8 @@
18306
19905
  }
18307
19906
  }
18308
19907
  },
18309
- "409": {
18310
- "description": "Either an earlier send under this `idempotency_key` had different content, or the sender custom domain has not completed outbound sending verification.",
19908
+ "429": {
19909
+ "description": "Rate limit exceeded. Retry after the duration in `error.extra.retry_after_seconds`. `limit_kind` identifies which bucket was exhausted.",
18311
19910
  "content": {
18312
19911
  "application/json": {
18313
19912
  "schema": {
@@ -18336,40 +19935,21 @@
18336
19935
  "required": [
18337
19936
  "error"
18338
19937
  ]
18339
- }
18340
- }
18341
- }
18342
- },
18343
- "429": {
18344
- "description": "Either the middleware send rate limit (`RATE_LIMITED`), the per-user daily cap of 100 sends (`RATE_LIMITED`), or the per-alias daily cap of 50 sends (`QUOTA_EXCEEDED`).",
18345
- "content": {
18346
- "application/json": {
18347
- "schema": {
18348
- "type": "object",
18349
- "properties": {
18350
- "error": {
18351
- "type": "object",
18352
- "properties": {
18353
- "code": {
18354
- "type": "string"
18355
- },
18356
- "message": {
18357
- "type": "string"
18358
- },
19938
+ },
19939
+ "examples": {
19940
+ "rateLimited": {
19941
+ "summary": "Per-key rate limit hit",
19942
+ "value": {
19943
+ "error": {
19944
+ "code": "RATE_LIMITED",
19945
+ "message": "rate limit exceeded",
18359
19946
  "extra": {
18360
- "type": "object",
18361
- "additionalProperties": {}
19947
+ "retry_after_seconds": 60,
19948
+ "limit_kind": "authenticated_per_key"
18362
19949
  }
18363
- },
18364
- "required": [
18365
- "code",
18366
- "message"
18367
- ]
19950
+ }
18368
19951
  }
18369
- },
18370
- "required": [
18371
- "error"
18372
- ]
19952
+ }
18373
19953
  }
18374
19954
  }
18375
19955
  }
@@ -18420,7 +20000,7 @@
18420
20000
  }
18421
20001
  },
18422
20002
  "502": {
18423
- "description": "The upstream email provider rejected the message. The row is persisted with `status: failed` and `error_code` / `error_message` set.",
20003
+ "description": "The upstream provider call failed or provider credentials are missing.",
18424
20004
  "content": {
18425
20005
  "application/json": {
18426
20006
  "schema": {
@@ -31031,6 +32611,10 @@
31031
32611
  "name": "EmailAliases",
31032
32612
  "description": "User-managed @wspc.app email aliases that route inbound mail to the user's inbox."
31033
32613
  },
32614
+ {
32615
+ "name": "EmailDomains",
32616
+ "description": "EmailDomains endpoints."
32617
+ },
31034
32618
  {
31035
32619
  "name": "Emails",
31036
32620
  "description": "Inbound and outbound email messages with attachment support."