askell-mcp 0.4.6 → 0.4.9

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/README.md CHANGED
@@ -126,7 +126,7 @@ Typical agent workflow:
126
126
  ## API notes (short)
127
127
 
128
128
  - **v1** — legacy paths like `/customers/`, `/subscriptions/` (no `/v2` prefix)
129
- - **v2** — current model: catalogs, quotes, checkouts, contracts, billing runs under `/v2/`
129
+ - **v2** — current model: catalogs, quotes, checkouts, contracts, billing runs, fulfillment orders under `/v2/`
130
130
  - **v2 discounts** — coupons: `GET/POST /v2/subscription-contracts/{id}/discount|apply-code|remove-discount` (one active). Quotes take `promotion_code` and, for an existing buyer, `customer` (id) so combo discounts + promo restrictions apply. First-period totals already include coupon + combo; `quote.recurring_*` include combo but not the coupon (`discount.recurring_final_amount` while the coupon is active). Recurring `finalize` needs a verified payment method even when due-now is 0. Not the v1 `discount` 0–100 field.
131
131
  - Paths use **trailing slashes**
132
132
  - Prefer **v2** for new integrations; v1 remains for existing ones
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "askell-mcp",
3
- "version": "0.4.6",
3
+ "version": "0.4.9",
4
4
  "description": "MCP server for the Askell payment and subscription API (Bun + stdio)",
5
5
  "author": "Neschadin Oleksandr",
6
6
  "license": "MIT",
@@ -67,6 +67,6 @@
67
67
  },
68
68
  "dependencies": {
69
69
  "@modelcontextprotocol/server": "2.0.0",
70
- "zod": "4.6.2"
70
+ "zod": "4.6.5"
71
71
  }
72
72
  }
@@ -55,6 +55,10 @@
55
55
  {
56
56
  "name": "V2 Billing Runs",
57
57
  "description": "Subscription V2 billing-run read and retry APIs, requires secret api key."
58
+ },
59
+ {
60
+ "name": "V2 Fulfillment",
61
+ "description": "Subscription V2 fulfillment order read APIs for warehouse integrations, requires secret api key."
58
62
  }
59
63
  ],
60
64
  "paths": {
@@ -1672,6 +1676,119 @@
1672
1676
  }
1673
1677
  ]
1674
1678
  }
1679
+ },
1680
+ "/v2/fulfillment-orders/": {
1681
+ "get": {
1682
+ "tags": [
1683
+ "V2 Fulfillment"
1684
+ ],
1685
+ "summary": "List fulfillment orders",
1686
+ "description": "Returns the authenticated account's fulfillment orders, newest first, with the same body the `fulfillment_order.*` webhooks carry. Intended for backfill and for polling-based reconciliation after a missed webhook delivery. Read-only. Requires a secret key, that the account uses subscription contracts, and that shipping is enabled for the account.",
1687
+ "parameters": [
1688
+ {
1689
+ "in": "query",
1690
+ "name": "status",
1691
+ "schema": {
1692
+ "type": "string",
1693
+ "enum": [
1694
+ "open",
1695
+ "partially_fulfilled",
1696
+ "fulfilled",
1697
+ "cancelled"
1698
+ ]
1699
+ },
1700
+ "description": "Filter by fulfillment status."
1701
+ },
1702
+ {
1703
+ "in": "query",
1704
+ "name": "contract",
1705
+ "schema": {
1706
+ "type": "integer"
1707
+ },
1708
+ "description": "Filter by subscription contract id."
1709
+ },
1710
+ {
1711
+ "in": "query",
1712
+ "name": "customer",
1713
+ "schema": {
1714
+ "type": "integer"
1715
+ },
1716
+ "description": "Filter by customer id."
1717
+ },
1718
+ {
1719
+ "in": "query",
1720
+ "name": "customer_reference",
1721
+ "schema": {
1722
+ "type": "string"
1723
+ },
1724
+ "description": "Filter by customer reference."
1725
+ },
1726
+ {
1727
+ "in": "query",
1728
+ "name": "updated_since",
1729
+ "schema": {
1730
+ "type": "string",
1731
+ "format": "date-time"
1732
+ },
1733
+ "description": "Only orders changed at or after this ISO 8601 timestamp. A bare date means the start of that day. This is the reconciliation filter: poll it with the `updated_at` of the last order you processed."
1734
+ },
1735
+ {
1736
+ "in": "query",
1737
+ "name": "created_since",
1738
+ "schema": {
1739
+ "type": "string",
1740
+ "format": "date-time"
1741
+ },
1742
+ "description": "Only orders created at or after this ISO 8601 timestamp. A bare date means the start of that day."
1743
+ },
1744
+ {
1745
+ "$ref": "#/components/parameters/PageSizeFilter"
1746
+ }
1747
+ ],
1748
+ "responses": {
1749
+ "200": {
1750
+ "$ref": "#/components/responses/V2FulfillmentOrderList"
1751
+ },
1752
+ "403": {
1753
+ "$ref": "#/components/responses/V2PermissionDenied"
1754
+ }
1755
+ },
1756
+ "security": [
1757
+ {
1758
+ "Secret-Api-Key": []
1759
+ }
1760
+ ]
1761
+ }
1762
+ },
1763
+ "/v2/fulfillment-orders/{fulfillmentOrderId}/": {
1764
+ "get": {
1765
+ "tags": [
1766
+ "V2 Fulfillment"
1767
+ ],
1768
+ "summary": "Get a fulfillment order",
1769
+ "description": "Returns one fulfillment order belonging to the authenticated account, with its lines, delivery address, shipping selection and booked shipments. Orders belonging to another account return `404`. Read-only. Requires a secret key.",
1770
+ "parameters": [
1771
+ {
1772
+ "$ref": "#/components/parameters/V2FulfillmentOrderId"
1773
+ }
1774
+ ],
1775
+ "responses": {
1776
+ "200": {
1777
+ "$ref": "#/components/responses/V2FulfillmentOrder"
1778
+ },
1779
+ "403": {
1780
+ "$ref": "#/components/responses/V2PermissionDenied"
1781
+ },
1782
+ "404": {
1783
+ "$ref": "#/components/responses/V2NotFound"
1784
+ }
1785
+ },
1786
+ "security": [
1787
+ {
1788
+ "Secret-Api-Key": []
1789
+ }
1790
+ ]
1791
+ }
1675
1792
  }
1676
1793
  },
1677
1794
  "components": {
@@ -3105,7 +3222,7 @@
3105
3222
  "properties": {
3106
3223
  "option": {
3107
3224
  "type": "integer",
3108
- "description": "Id of one of the account's active shipping options."
3225
+ "description": "Id of one of the account's active shipping options. An option with a rate table is priced for the delivery address's zip code (the request's `delivery_address`, else the customer's address) and the weight of the goods; when the table has no rate for that delivery the request fails with `400`, `shipping` describing why and `shipping_code` `shipping_not_available`."
3109
3226
  },
3110
3227
  "location_id": {
3111
3228
  "type": "string",
@@ -3164,6 +3281,14 @@
3164
3281
  "currency": {
3165
3282
  "type": "string"
3166
3283
  },
3284
+ "zone_name": {
3285
+ "type": "string",
3286
+ "description": "The shipping zone that priced the selection, or an empty string when the option is flat-priced or the rate applied to every zip code."
3287
+ },
3288
+ "weight_band": {
3289
+ "type": "string",
3290
+ "description": "The weight band that priced the selection as `min-max` in grams (`max` empty for an open-ended band), or an empty string for a flat-priced option."
3291
+ },
3167
3292
  "location_id": {
3168
3293
  "type": "string"
3169
3294
  },
@@ -3172,6 +3297,51 @@
3172
3297
  },
3173
3298
  "location_address": {
3174
3299
  "type": "string"
3300
+ },
3301
+ "location": {
3302
+ "$ref": "#/components/schemas/V2PickupLocation"
3303
+ }
3304
+ }
3305
+ },
3306
+ "V2PickupLocation": {
3307
+ "type": "object",
3308
+ "nullable": true,
3309
+ "description": "The chosen pickup location as a structured block an external fulfillment system can book against, or null when the selection has none (home delivery, freight, store pickup without an address). Derived from the shipping provider's own payload, with the flat `location_*` values as the fallback.",
3310
+ "properties": {
3311
+ "id": {
3312
+ "type": "string",
3313
+ "description": "The provider's id for the location."
3314
+ },
3315
+ "name": {
3316
+ "type": "string"
3317
+ },
3318
+ "address": {
3319
+ "type": "string",
3320
+ "description": "One-line address, composed from street, zip code and town when the provider does not supply one."
3321
+ },
3322
+ "street": {
3323
+ "type": "string"
3324
+ },
3325
+ "zip_code": {
3326
+ "type": "string"
3327
+ },
3328
+ "town": {
3329
+ "type": "string"
3330
+ },
3331
+ "external_id": {
3332
+ "type": "string",
3333
+ "nullable": true,
3334
+ "description": "The location's id in the provider's upstream system, when it exposes one."
3335
+ },
3336
+ "latitude": {
3337
+ "type": "number",
3338
+ "format": "double",
3339
+ "nullable": true
3340
+ },
3341
+ "longitude": {
3342
+ "type": "number",
3343
+ "format": "double",
3344
+ "nullable": true
3175
3345
  }
3176
3346
  }
3177
3347
  },
@@ -3765,6 +3935,25 @@
3765
3935
  "type": "string",
3766
3936
  "format": "decimal"
3767
3937
  },
3938
+ "shipping_fee": {
3939
+ "type": "object",
3940
+ "nullable": true,
3941
+ "description": "Sendingargjald sem er hluti af checkout-samtölunum, eða null.",
3942
+ "properties": {
3943
+ "amount": {
3944
+ "type": "string"
3945
+ },
3946
+ "subtotal_amount": {
3947
+ "type": "string"
3948
+ },
3949
+ "tax_amount": {
3950
+ "type": "string"
3951
+ },
3952
+ "total_amount": {
3953
+ "type": "string"
3954
+ }
3955
+ }
3956
+ },
3768
3957
  "contract_id": {
3769
3958
  "type": "integer",
3770
3959
  "nullable": true
@@ -3932,6 +4121,25 @@
3932
4121
  "type": "string",
3933
4122
  "format": "decimal"
3934
4123
  },
4124
+ "shipping_fee": {
4125
+ "type": "object",
4126
+ "nullable": true,
4127
+ "description": "Sendingargjald sem er hluti af tilboðinu (samtölur innihalda það), eða null.",
4128
+ "properties": {
4129
+ "amount": {
4130
+ "type": "string"
4131
+ },
4132
+ "subtotal_amount": {
4133
+ "type": "string"
4134
+ },
4135
+ "tax_amount": {
4136
+ "type": "string"
4137
+ },
4138
+ "total_amount": {
4139
+ "type": "string"
4140
+ }
4141
+ }
4142
+ },
3935
4143
  "first_period_recurring_subtotal_amount": {
3936
4144
  "type": "string",
3937
4145
  "format": "decimal"
@@ -4582,6 +4790,211 @@
4582
4790
  }
4583
4791
  }
4584
4792
  },
4793
+ "V2FulfillmentAddress": {
4794
+ "type": "object",
4795
+ "nullable": true,
4796
+ "description": "The delivery address as it stood when the order was created. This is the order's own snapshot, so editing the contract's address afterwards does not change it.",
4797
+ "properties": {
4798
+ "id": {
4799
+ "type": "integer"
4800
+ },
4801
+ "delivery_name": {
4802
+ "type": "string",
4803
+ "nullable": true
4804
+ },
4805
+ "address_1": {
4806
+ "type": "string"
4807
+ },
4808
+ "address_2": {
4809
+ "type": "string",
4810
+ "nullable": true
4811
+ },
4812
+ "address_3": {
4813
+ "type": "string",
4814
+ "nullable": true
4815
+ },
4816
+ "zip_code": {
4817
+ "type": "string"
4818
+ },
4819
+ "city": {
4820
+ "type": "string",
4821
+ "nullable": true
4822
+ },
4823
+ "state": {
4824
+ "type": "string",
4825
+ "nullable": true
4826
+ },
4827
+ "country": {
4828
+ "type": "string",
4829
+ "description": "ISO 3166-1 alpha-2 country code."
4830
+ }
4831
+ }
4832
+ },
4833
+ "V2FulfillmentOrderLine": {
4834
+ "type": "object",
4835
+ "properties": {
4836
+ "id": {
4837
+ "type": "integer"
4838
+ },
4839
+ "product_id": {
4840
+ "type": "integer",
4841
+ "nullable": true
4842
+ },
4843
+ "product_reference": {
4844
+ "type": "string",
4845
+ "nullable": true,
4846
+ "description": "The seller's own product reference, i.e. the SKU a warehouse picks by. Null when the catalog product has been deleted."
4847
+ },
4848
+ "product_name": {
4849
+ "type": "string",
4850
+ "description": "Product name at the time the order was created."
4851
+ },
4852
+ "quantity": {
4853
+ "type": "integer"
4854
+ },
4855
+ "quantity_fulfilled": {
4856
+ "type": "integer",
4857
+ "description": "How many units have been included in a shipment."
4858
+ }
4859
+ }
4860
+ },
4861
+ "V2Fulfillment": {
4862
+ "type": "object",
4863
+ "description": "A shipment booked for (part of) an order.",
4864
+ "properties": {
4865
+ "id": {
4866
+ "type": "integer"
4867
+ },
4868
+ "order_id": {
4869
+ "type": "integer"
4870
+ },
4871
+ "handler": {
4872
+ "type": "string",
4873
+ "enum": [
4874
+ "dropp",
4875
+ "posturinn",
4876
+ "store_pickup"
4877
+ ]
4878
+ },
4879
+ "status": {
4880
+ "type": "string",
4881
+ "enum": [
4882
+ "pending",
4883
+ "booked",
4884
+ "failed",
4885
+ "shipped",
4886
+ "delivered",
4887
+ "cancelled"
4888
+ ]
4889
+ },
4890
+ "provider_order_id": {
4891
+ "type": "string",
4892
+ "description": "The provider's own identifier for the shipment, such as a Dropp order UUID or a Pósturinn shipment id."
4893
+ },
4894
+ "tracking_number": {
4895
+ "type": "string"
4896
+ },
4897
+ "weight_grams": {
4898
+ "type": "integer",
4899
+ "nullable": true,
4900
+ "description": "Package weight used for the booking."
4901
+ },
4902
+ "error": {
4903
+ "type": "string",
4904
+ "description": "The last booking error, empty when the booking succeeded."
4905
+ },
4906
+ "booked_at": {
4907
+ "type": "string",
4908
+ "format": "date-time",
4909
+ "nullable": true
4910
+ },
4911
+ "created_at": {
4912
+ "type": "string",
4913
+ "format": "date-time"
4914
+ },
4915
+ "updated_at": {
4916
+ "type": "string",
4917
+ "format": "date-time"
4918
+ }
4919
+ }
4920
+ },
4921
+ "V2FulfillmentOrder": {
4922
+ "type": "object",
4923
+ "description": "A physical order generated by a paid billing run. This is also the body of the `fulfillment_order.*` webhooks.",
4924
+ "properties": {
4925
+ "id": {
4926
+ "type": "integer"
4927
+ },
4928
+ "number": {
4929
+ "type": "integer",
4930
+ "description": "Per-account sequential order number."
4931
+ },
4932
+ "status": {
4933
+ "type": "string",
4934
+ "enum": [
4935
+ "open",
4936
+ "partially_fulfilled",
4937
+ "fulfilled",
4938
+ "cancelled"
4939
+ ]
4940
+ },
4941
+ "contract_id": {
4942
+ "type": "integer"
4943
+ },
4944
+ "billing_run_id": {
4945
+ "type": "integer",
4946
+ "description": "The successful billing run that generated this order. Each run generates at most one order."
4947
+ },
4948
+ "customer_id": {
4949
+ "type": "integer"
4950
+ },
4951
+ "customer_reference": {
4952
+ "type": "string"
4953
+ },
4954
+ "customer_name": {
4955
+ "type": "string"
4956
+ },
4957
+ "customer_email": {
4958
+ "type": "string",
4959
+ "nullable": true
4960
+ },
4961
+ "delivery_address": {
4962
+ "$ref": "#/components/schemas/V2FulfillmentAddress"
4963
+ },
4964
+ "shipping_selection": {
4965
+ "$ref": "#/components/schemas/V2ShippingSelection"
4966
+ },
4967
+ "lines": {
4968
+ "type": "array",
4969
+ "items": {
4970
+ "$ref": "#/components/schemas/V2FulfillmentOrderLine"
4971
+ }
4972
+ },
4973
+ "fulfillments": {
4974
+ "type": "array",
4975
+ "description": "Shipments booked for this order. Empty until one is booked, and always empty for an account with no shipping providers configured.",
4976
+ "items": {
4977
+ "$ref": "#/components/schemas/V2Fulfillment"
4978
+ }
4979
+ },
4980
+ "estimated_weight_grams": {
4981
+ "type": "integer",
4982
+ "nullable": true,
4983
+ "description": "Sum of the products' chargeable unit weights, or null when any line lacks weight data, so a partial estimate is never mistaken for a total."
4984
+ },
4985
+ "metadata": {
4986
+ "$ref": "#/components/schemas/V2Metadata"
4987
+ },
4988
+ "created_at": {
4989
+ "type": "string",
4990
+ "format": "date-time"
4991
+ },
4992
+ "updated_at": {
4993
+ "type": "string",
4994
+ "format": "date-time"
4995
+ }
4996
+ }
4997
+ },
4585
4998
  "V2SubscriptionContractCustomer": {
4586
4999
  "type": "object",
4587
5000
  "properties": {
@@ -5108,6 +5521,17 @@
5108
5521
  "type": "integer"
5109
5522
  }
5110
5523
  },
5524
+ "V2FulfillmentOrderId": {
5525
+ "name": "fulfillmentOrderId",
5526
+ "in": "path",
5527
+ "description": "Fulfillment order id. This is the order's `id`, not its per-account `number`.",
5528
+ "required": true,
5529
+ "style": "simple",
5530
+ "explode": false,
5531
+ "schema": {
5532
+ "type": "integer"
5533
+ }
5534
+ },
5111
5535
  "V2CustomerReference": {
5112
5536
  "name": "customerReference",
5113
5537
  "in": "path",
@@ -5470,6 +5894,70 @@
5470
5894
  }
5471
5895
  }
5472
5896
  },
5897
+ "V2PermissionDenied": {
5898
+ "description": "The account may not use this endpoint, for example because it does not use subscription contracts or does not have shipping enabled",
5899
+ "content": {
5900
+ "application/json": {
5901
+ "schema": {
5902
+ "type": "object",
5903
+ "properties": {
5904
+ "detail": {
5905
+ "type": "string"
5906
+ }
5907
+ }
5908
+ }
5909
+ }
5910
+ }
5911
+ },
5912
+ "V2FulfillmentOrder": {
5913
+ "description": "V2 fulfillment order",
5914
+ "content": {
5915
+ "application/json": {
5916
+ "schema": {
5917
+ "$ref": "#/components/schemas/V2FulfillmentOrder"
5918
+ }
5919
+ }
5920
+ }
5921
+ },
5922
+ "V2FulfillmentOrderList": {
5923
+ "description": "V2 fulfillment order list",
5924
+ "content": {
5925
+ "application/json": {
5926
+ "schema": {
5927
+ "anyOf": [
5928
+ {
5929
+ "type": "array",
5930
+ "items": {
5931
+ "$ref": "#/components/schemas/V2FulfillmentOrder"
5932
+ }
5933
+ },
5934
+ {
5935
+ "type": "object",
5936
+ "properties": {
5937
+ "count": {
5938
+ "type": "integer"
5939
+ },
5940
+ "next": {
5941
+ "type": "string",
5942
+ "nullable": true
5943
+ },
5944
+ "previous": {
5945
+ "type": "string",
5946
+ "nullable": true
5947
+ },
5948
+ "results": {
5949
+ "type": "array",
5950
+ "items": {
5951
+ "$ref": "#/components/schemas/V2FulfillmentOrder"
5952
+ }
5953
+ }
5954
+ }
5955
+ }
5956
+ ]
5957
+ }
5958
+ }
5959
+ }
5960
+ },
5473
5961
  "V2BillingRun": {
5474
5962
  "description": "V2 billing run",
5475
5963
  "content": {
@@ -9,13 +9,15 @@ Askell POSTs signed JSON to each URL you register. Verify \`Hook-HMAC\` before p
9
9
  Headers:
10
10
  - Hook-HMAC: base64 HMAC-SHA512 of the **raw body** (secret = \`hmac_secret\` from webhook create)
11
11
  - Hook-Event: event type (\`subscription.renewed\`, \`payment.changed\`, or a family wildcard \`subscription.*\`)
12
- - Hook-API-Version: \`v1\` for plan/subscription/customer/payment/checkout, \`v2\` for subscription_contract / billing_run
12
+ - Hook-API-Version: \`v1\` for plan/subscription/customer/payment/checkout, \`v2\` for subscription_contract / billing_run / fulfillment_order
13
13
 
14
- ## Body shape (not in OpenAPI)
14
+ ## Body shape
15
15
 
16
16
  JSON body **is the event object**. It is **not** \`{ event, data }\`.
17
17
 
18
- Upstream swagger used to document a dummy \`POST /your-webhook-url/\` with \`SubscriptionMultiLite\` (\`{ customer, subscriptions[] }\`). \`sync-specs\` strips that path. Inbound payloads are still undocumented in OpenAPI — this resource is the overlay.
18
+ Upstream swagger used to document a dummy \`POST /your-webhook-url/\` with \`SubscriptionMultiLite\` (\`{ customer, subscriptions[] }\`). \`sync-specs\` strips that path.
19
+
20
+ Most inbound families are still undocumented in OpenAPI — this resource is the overlay. Exception: \`fulfillment_order.*\` body **is** \`V2FulfillmentOrder\` (same as \`GET /v2/fulfillment-orders/{id}/\`). Live https://docs.askell.is/en/api/webhooks.html does not list this family yet.
19
21
 
20
22
  Rare historical payloads used \`{ event, data, ref?, sender? }\`. If both \`event\` and \`data\` are objects, use \`data\`.
21
23
 
@@ -59,6 +61,13 @@ V2 migration: \`subscription.*\` is **not** aliased onto the new contract (paylo
59
61
 
60
62
  ### checkout.* (v1)
61
63
  \`created\`, \`changed\` — \`token\`, \`checkout_url\`, \`status\`.
64
+
65
+ ### fulfillment_order.* (v2)
66
+ Family wildcard \`fulfillment_order.*\`. Concrete event names beyond the family are not listed in swagger or live webhook docs — do not invent \`created\`/\`changed\`.
67
+
68
+ Body = \`V2FulfillmentOrder\` = \`GET /v2/fulfillment-orders/{fulfillmentOrderId}/\` (list items are the same object). Physical order from a paid billing run (\`billing_run_id\`; at most one order per run). \`delivery_address\` is a snapshot (later contract address edits do not change it). \`shipping_selection\` is the checkout snapshot. \`fulfillments[]\` are booked shipments (empty until booked / if no shipping providers). Status: \`open\` | \`partially_fulfilled\` | \`fulfilled\` | \`cancelled\`.
69
+
70
+ Register \`fulfillment_order.*\` on \`POST /webhooks/\`. REST backfill: \`GET /v2/fulfillment-orders/?updated_since=\` (secret; newest first; \`403\` if the account has no subscription contracts or shipping is disabled). Read-only — no mark-shipped mutate.
62
71
  `;
63
72
 
64
73
  export function registerResources(server: McpServer): void {
package/src/server.ts CHANGED
@@ -37,7 +37,7 @@ Workflow:
37
37
 
38
38
  API models:
39
39
  - v1 (legacy): PlanVariant + Subscription at paths like /subscriptions/, /customers/. Still supported for existing integrations.
40
- - v2 (current): Catalog, bundles, quotes, checkouts, subscription contracts, billing runs under /v2/. Prefer v2 for new integrations.
40
+ - v2 (current): Catalog, bundles, quotes, checkouts, subscription contracts, billing runs, fulfillment orders under /v2/. Prefer v2 for new integrations.
41
41
  - Prose docs at https://docs.askell.is/api/ may describe flows (embedded checkout, 3D Secure, wallet passes) not fully listed in OpenAPI.
42
42
 
43
43
  API layout:
@@ -55,10 +55,13 @@ V2 checkout notes:
55
55
  - checkout_url on V2 checkouts points to the API object URL, not a hosted payment page.
56
56
  - GET contract.subscriber_page is the customer-facing subscription management URL (readOnly, nullable). Not checkout_url, not v1 /public/payments/{id}/ (hosted signup). Do not send it on create/patch.
57
57
  - finalize: a recurring offer needs a verified payment method even when due-now/total is 0 (trial or fully discounted first period). Only a free one-time purchase finalizes without one. Live docs still say "unless 0 ISK" — ignore that; bundled OpenAPI is right.
58
- - Hosted POST /v2/checkouts/: shipping {option, location_id?} is required when the offer has physical products and the account has active shipping options. No shipping-options list in OpenAPI (ids are account config). Pickup options need location_id. Snapshot is contract.shipping_selection, not on V2Checkout.
58
+ - Hosted POST /v2/checkouts/: shipping {option, location_id?} is required when the offer has physical products and the account has active shipping options. No shipping-options list in OpenAPI (ids are account config). Pickup options need location_id. Snapshot is contract.shipping_selection (plus location / zone_name / weight_band), not on V2Checkout. Rate-table option with no zip/weight rate: 400, shipping_code shipping_not_available. Quote/checkout totals already include shipping_fee when present.
59
59
  - Hosted iframe (not askell.js): POST /v2/checkouts/ and POST .../payment-method-registrations/ take allowed_origin (one origin, no path; http only localhost/loopback). Replaces account-level frame-ancestors; GET empty string = account-level. Rejected on /v2/checkout-sessions/ (sales-channel allowed_origins[]).
60
60
  - Embedded checkout uses POST /v2/checkout-sessions/ plus browser session-token sub-paths (widget collects address/shipping; see docs, not all in OpenAPI).
61
61
 
62
+ V2 fulfillment (warehouse, read-only):
63
+ - GET /v2/fulfillment-orders/ and GET /v2/fulfillment-orders/{id}/. Same body as fulfillment_order.* webhooks (V2FulfillmentOrder). Secret key. 403 if the account has no subscription contracts or shipping is disabled. Poll updated_since after a missed webhook (newest first). No POST/PATCH — cannot mark shipped via the API.
64
+
62
65
  Auth:
63
66
  - Most endpoints need the secret API key.
64
67
  - Only temporary payment method and checkout status endpoints use the public key.
@@ -71,7 +74,7 @@ Safety:
71
74
 
72
75
  Resources:
73
76
  - askell://spec/v1 and askell://spec/v2 — bundled OpenAPI
74
- - askell://docs/webhook-events — inbound webhook payloads (not in OpenAPI; dummy /your-webhook-url/ is stripped on sync), HMAC-SHA512, /webhooks/ hmac_secret`;
77
+ - askell://docs/webhook-events — inbound webhook payloads (most families not in OpenAPI; fulfillment_order.* is V2FulfillmentOrder), HMAC-SHA512, /webhooks/ hmac_secret`;
75
78
  }
76
79
 
77
80
  export function createServer(config: AppConfig): McpServer {