@nuanu-ai/agentify-contracts 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.js CHANGED
@@ -42,23 +42,23 @@
42
42
  * later by a merchant who took an order on and now has the goods or has run
43
43
  * out of them. The order machine keeps the same two apart, and in the
44
44
  * synchronous mode it is the returned answer that exists and the explicit
45
- * calls that do not. The addendum of 2026-08-26 to ADR-0004 settles this; §2
46
- * of that decision, read alone, leaves the synchronous handler with no address
47
- * at all.
45
+ * calls that do not. ADR-0004 §2 settles this.
48
46
  */
49
47
  import { z } from "zod";
50
- import { CardSchema, MerchantCardSchema, PublicCardSchema } from "./card.js";
48
+ import { CardSchema, FulfillmentSchema, MerchantCardSchema, PublicCardSchema, SellerSchema, } from "./card.js";
51
49
  import { WorkerEnvelopeSchema } from "./envelope.js";
52
50
  import { AcceptanceSchema, DeliverySchema, HandlerAnswerSchema, RefusalSchema } from "./handler.js";
53
- import { CabinetKeySchema, DisabledKeySchema, ForgottenCabinetKeySchema, IssuedKeySchema, IssueKeyRequestSchema, MerchantKeyListSchema, PayoutWalletRequestSchema, PayoutWalletSchema, RegisteredMerchantSchema, RegistrationRequestSchema, SellerNameRequestSchema, SellerNameSchema, } from "./merchant.js";
51
+ import { DisabledKeySchema, IssuedKeySchema, IssueKeyRequestSchema, MerchantKeyListSchema, PayoutWalletSchema, SellerNameRequestSchema, SellerNameSchema, } from "./merchant.js";
54
52
  import { OrderSchema } from "./order.js";
55
53
  import { OrderStatusSchema } from "./order-status.js";
56
54
  import { ParamNameSchema } from "./param-spec.js";
57
- import { IdentifierSchema, SalePriceSchema } from "./primitives.js";
55
+ import { IdentifierSchema, OpenWordSchema, SalePriceSchema, TimestampSchema, } from "./primitives.js";
58
56
  import { QuoteResponseSchema } from "./quote.js";
59
57
  import { ReceiptSchema } from "./receipt.js";
60
58
  import { CallErrorSchema, OrderCallResultSchema, PublishResultSchema } from "./results.js";
61
59
  import { SellingStateSchema } from "./selling.js";
60
+ import { ShipToSchema } from "./ship-to.js";
61
+ import { RecordedShipmentSchema } from "./shipment.js";
62
62
  /**
63
63
  * An order together with the word for where it stands.
64
64
  *
@@ -304,9 +304,17 @@ export const PurchaseRequestSchema = z
304
304
  // The same dropped key as everywhere this contract parses free-form names;
305
305
  // see `PROTOTYPE_KEY_IS_DROPPED` in `param-spec.ts`.
306
306
  params: z.record(ParamNameSchema, z.unknown()),
307
+ /**
308
+ * Where a parcel goes (ADR-0032). Required on the purchase of a card whose
309
+ * fulfillment is "ship", and refused on any other: only a parcel needs an
310
+ * address, and one sent with anything else would be a buyer's details
311
+ * handed over for nothing. Sent again with the payment it must be the one
312
+ * that was priced, or be left out.
313
+ */
314
+ ship_to: ShipToSchema.optional(),
307
315
  })
308
316
  .meta({
309
- description: "What an agent supplies to buy: the purchase parameters, empty for a product that needs none. The names are held to the shape a card could have declared and no further — that these values fit this card is checked against that card at the moment of purchase.",
317
+ description: "What an agent supplies to buy: the purchase parameters, empty for a product that needs none, and for a parcel the address it goes to. The names are held to the shape a card could have declared and no further — that these values fit this card is checked against that card at the moment of purchase. ship_to is required when the card's fulfillment is \"ship\" and refused on any other card. The merchant's price question receives only its locality; the merchant receives the whole address once the order is paid. Sent again with the payment it must be the same address, or be left out, in which case the payment is for the address that was priced; a different one is refused before the payment is checked, and is a new purchase. That holds while Agentify still holds the address: once it has let go of it — the order taken on, ended, or owing a refund without being taken on — there is nothing to compare with, a payment's ship_to is checked for its shape only, and the answer is the order as it stands.",
310
318
  });
311
319
  /**
312
320
  * What became of a purchase, told to the agent that made it.
@@ -344,8 +352,17 @@ export const PurchaseRequestSchema = z
344
352
  * payment that failed its check both still arrive as a bare `rejected`,
345
353
  * because neither was worded by anybody.
346
354
  */
355
+ /**
356
+ * A shipment's expected window as an agent reads it: open, as the shipment
357
+ * around it is, so a field added to the window later is ignored by an agent
358
+ * built before it rather than making the whole status unreadable (ADR-0006
359
+ * §5). Opening it makes a new schema, which carries none of the old one's
360
+ * description, so the description is carried across.
361
+ */
362
+ const closedWindow = RecordedShipmentSchema.shape.estimated_delivery.unwrap();
363
+ const openWindow = closedWindow.loose().meta(closedWindow.meta() ?? {});
347
364
  export const AgentOrderStatusSchema = z
348
- .strictObject({
365
+ .looseObject({
349
366
  order_id: IdentifierSchema,
350
367
  /**
351
368
  * Where this order is read again: the whole address of its status route,
@@ -379,7 +396,16 @@ export const AgentOrderStatusSchema = z
379
396
  status_url: z
380
397
  .url()
381
398
  .regex(/^https?:\/\//, "an order's status address is a whole http or https address, not a path"),
382
- status: OrderStatusSchema,
399
+ /**
400
+ * Where the order stands, as a word whose known values are listed beside
401
+ * it (ADR-0006 §5). The storefront has no version, so a word added later
402
+ * reaches agents that still hold this contract: one they do not know is
403
+ * not an ending they know, and they ask again later rather than buy again
404
+ * on its strength. The merchant's own view of the order reads the same
405
+ * words from a closed list. Two branches, as a card's mode is
406
+ * (`PublicCardSchema`), so the known words still cross into the export.
407
+ */
408
+ status: z.union([OrderStatusSchema, OpenWordSchema]),
383
409
  /**
384
410
  * The price this order was priced at, or null where nobody ever named one
385
411
  * for it. It is the order's own price and not the card's number: a card
@@ -393,7 +419,7 @@ export const AgentOrderStatusSchema = z
393
419
  * field's, and a reader taking this for an amount charged would be
394
420
  * reconciling against sales that never happened.
395
421
  */
396
- price: SalePriceSchema.nullable(),
422
+ price: SalePriceSchema.loose().nullable(),
397
423
  /**
398
424
  * The goods, once they are the buyer's — the delivery as the merchant
399
425
  * wrote it, and null until then.
@@ -406,12 +432,33 @@ export const AgentOrderStatusSchema = z
406
432
  * says null for exactly as long as that is true.
407
433
  */
408
434
  delivered: DeliverySchema.nullable(),
435
+ /**
436
+ * A parcel's shipment once its merchant has recorded it, and null until
437
+ * then (ADR-0033). Present on a parcel's order and on no other.
438
+ *
439
+ * It is beside `delivered` rather than in it because nothing reached the
440
+ * agent: what the merchant handed a carrier is not goods in hand, and
441
+ * `delivered` stays null on a parcel for that reason. It is the last thing
442
+ * Agentify knows about the parcel.
443
+ */
444
+ shipment: RecordedShipmentSchema.extend({ estimated_delivery: openWindow.optional() })
445
+ .loose()
446
+ .nullable()
447
+ .optional(),
448
+ /**
449
+ * When a parcel has to be with its carrier by, as an absolute instant: the
450
+ * card's time to ship counted from the charge, and null until the order is
451
+ * paid (ADR-0033). Present on a parcel's order and on no other. A parcel
452
+ * not shipped by then leaves its merchant owing a refund, which this
453
+ * document then says.
454
+ */
455
+ ship_by: TimestampSchema.nullable().optional(),
409
456
  /**
410
457
  * Whether the money behind this purchase was real.
411
458
  *
412
459
  * The receipt a merchant reads carries this word already, and the buyer's
413
460
  * own view of the same purchase is the one place it matters more: a
414
- * sandbox settles against nothing (ADR-0008) and every other field here
461
+ * sandbox settles against nothing (ADR-0020) and every other field here
415
462
  * reads exactly as it would after a real charge. Without it, this document
416
463
  * is indistinguishable from proof of a purchase that moved money, which is
417
464
  * the one thing it must never be mistaken for.
@@ -443,10 +490,21 @@ export const AgentOrderStatusSchema = z
443
490
  * present pair is always somebody's actual answer rather than a word this
444
491
  * gateway picked for them.
445
492
  */
446
- refusal: RefusalSchema.optional(),
493
+ refusal: RefusalSchema.loose().optional(),
494
+ /**
495
+ * Who sold it: the name and the site the merchant gave, read as their
496
+ * merchant stands now (ADR-0034).
497
+ *
498
+ * An agent holding an order and nothing else is the one with a question
499
+ * the order cannot answer — a parcel that did not arrive, a return — and
500
+ * this is where it learns where to take it. It is what the catalog already
501
+ * shows every agent beside the merchant's cards and nothing more: not the
502
+ * merchant's account, not their own key for the product, not the card.
503
+ */
504
+ seller: SellerSchema.loose(),
447
505
  })
448
506
  .meta({
449
- description: 'What became of one purchase, in the words an agent and a merchant both read: where the order stands, what it was priced at, the goods once they are the buyer\'s, and why the merchant would not sell where that is what ended it. "status_url" is where this document is read again — the whole address of the order\'s status route, and the place an agent that bought goods that come later collects them. It is absolute and called as it stands, and it is the address the gateway is configured to answer as, never one taken from the request that asked. It is smaller than the merchant\'s own view of the same order on purpose — no merchant, no merchant\'s own key for the product, none of the purchase parameters and nothing about any other order. The price is what the buyer was asked for and not proof that anything was charged: an order that was priced and then ended without a sale still carries it, and the status is what says which happened. A null price means nobody ever named one for this order, and a null delivery means there are no goods here to hand over; both fields are always present, because an absent field is a silence a reader cannot tell from an oversight. "refusal" is the exception and is present only where a merchant refused: their own short code to branch on and their own sentence to show, carried across unchanged. The code is an open set — "out_of_stock", "invalid_params" and "cannot_fulfill" are read the same way by everybody, and a merchant whose reason fits none of them sends their own word, so an unfamiliar code has to fall through to the sentence rather than break a reader. An absent "refusal" means there is no refusal to quote and never that one was dropped, which leaves two endings still coarse: "rejected" also covers a product that was gone and a payment that failed its check, and neither of those was worded by anybody — the first because a price answer of "not available" carries no words, the second because it is refused at the door in an error envelope instead. A null delivery is likewise not a promise that no goods were ever made: a purchase whose charge failed or went unanswered can leave goods the buyer has not paid for, and this document withholds them rather than describing them. Every answer says whether the money behind the purchase was real: a gateway settling against nothing produces every other field here exactly as a real charge would, so a reader taking this for proof of a payment has to read that word first.',
507
+ description: 'What became of one purchase, in the words an agent and a merchant both read: where the order stands, what it was priced at, the goods once they are the buyer\'s, and why the merchant would not sell where that is what ended it. "status_url" is where this document is read again — the whole address of the order\'s status route, and the place an agent that bought goods that come later collects them. It is absolute and called as it stands, and it is the address the gateway is configured to answer as, never one taken from the request that asked. It is smaller than the merchant\'s own view of the same order on purpose — no merchant account, no merchant\'s own key for the product, none of the purchase parameters and nothing about any other order. The price is what the buyer was asked for and not proof that anything was charged: an order that was priced and then ended without a sale still carries it, and the status is what says which happened. A null price means nobody ever named one for this order, and a null delivery means there are no goods here to hand over; both fields are always present, because an absent field is a silence a reader cannot tell from an oversight. A parcel\'s order also carries "shipment", its merchant\'s record of handing it to a carrier and null until then, and "ship_by", the instant it has to be with a carrier by and null until it is paid; no other order carries either. On a parcel "delivered" stays null, because nothing reached the agent: its status reads "shipped" once the shipment is recorded, and that is the last word Agentify has about it. "refusal" is the exception and is present only where a merchant refused: their own short code to branch on and their own sentence to show, carried across unchanged. The code is an open set — "out_of_stock", "invalid_params" and "cannot_fulfill" are read the same way by everybody, and a merchant whose reason fits none of them sends their own word, so an unfamiliar code has to fall through to the sentence rather than break a reader. An absent "refusal" means there is no refusal to quote and never that one was dropped, which leaves two endings still coarse: "rejected" also covers a product that was gone and a payment that failed its check, and neither of those was worded by anybody — the first because a price answer of "not available" carries no words, the second because it is refused at the door in an error envelope instead. A null delivery is likewise not a promise that no goods were ever made: a purchase whose charge failed or went unanswered can leave goods the buyer has not paid for, and this document withholds them rather than describing them. Every answer says whether the money behind the purchase was real: a gateway settling against nothing produces every other field here exactly as a real charge would, so a reader taking this for proof of a payment has to read that word first. "seller" is who sold it, as the merchant gave it and as the catalog shows it beside their cards: the name and the site of their shop, which Agentify did not check, and the site is where to take what this document cannot answer. "status" is a word whose known values are listed beside it, and more may be added: a word a reader does not know is not an ending it knows, so it asks again later and does not buy again on its strength. This document and every part inside it may also gain fields; a reader ignores the ones it does not know.',
450
508
  });
451
509
  /**
452
510
  * The catalog as an agent reads it.
@@ -456,23 +514,40 @@ export const AgentOrderStatusSchema = z
456
514
  * Until then it makes no claim about completeness, and that is stated in the
457
515
  * document itself: an agent must not read the absence of a field about paging
458
516
  * as a promise that there is nothing more.
517
+ *
518
+ * It is read card by card (ADR-0006 §5). The storefront has no version, so a
519
+ * card of a mode or a shape added later reaches agents that still hold this
520
+ * contract, and one card they cannot read must leave every other card for
521
+ * sale. So the page holds its items as whatever they are, and `cardsOf` reads
522
+ * each one on its own against the card an agent reads.
459
523
  */
460
524
  export const CatalogPageSchema = z
461
- .strictObject({
462
- items: z.array(PublicCardSchema),
525
+ .looseObject({
526
+ items: z.array(z.unknown().meta({
527
+ description: "One product, read on its own as a public_card. An item a reader cannot read as one is passed over, and the rest of the page stands.",
528
+ })),
463
529
  })
464
530
  .meta({
465
- description: "Products offered for sale, as an agent reads them. This document does not say whether it is the whole catalog: paging is not designed, and when it is, this object grows the field that answers it. Until then the absence of such a field is not a promise that there is no more.",
531
+ description: "Products offered for sale, as an agent reads them. This document does not say whether it is the whole catalog: paging is not designed, and when it is, this object grows the field that answers it. Until then the absence of such a field is not a promise that there is no more. Each item is read on its own as a public_card, and one a reader cannot read — a mode or a shape added after the contract it holds — is passed over while the rest of the page stands. This document may also gain fields; a reader ignores the ones it does not know.",
466
532
  });
467
533
  /**
468
- * The methods this surface uses.
534
+ * The cards of a catalog page a reader of this contract can buy from, each read
535
+ * on its own.
469
536
  *
470
- * `DELETE` is here for the one call that removes rows rather than marking them,
471
- * and it earns the third verb rather than being folded into a POST: what it
472
- * does to a key is not what disabling one does, and a surface that spelled both
473
- * as a POST would be inviting a reader to think it was.
537
+ * Two kinds of item are passed over, and the rest of the page stands. One that
538
+ * does not read as a card at all, and one that does but is sold in a mode this
539
+ * contract does not name: its reader cannot know when such goods arrive or when
540
+ * the money moves, so it must not buy on it.
474
541
  */
475
- export const HTTP_METHODS = Object.freeze(["GET", "POST", "DELETE"]);
542
+ export const cardsOf = (page) => page.items.flatMap((item) => {
543
+ const read = PublicCardSchema.safeParse(item);
544
+ if (!read.success)
545
+ return [];
546
+ const mode = FulfillmentSchema.safeParse(read.data.fulfillment);
547
+ return mode.success ? [{ ...read.data, fulfillment: mode.data }] : [];
548
+ });
549
+ /** The methods this surface uses. */
550
+ export const HTTP_METHODS = Object.freeze(["GET", "POST"]);
476
551
  /**
477
552
  * Which door a call is behind.
478
553
  *
@@ -660,7 +735,6 @@ export const ERROR_CODES = Object.freeze([
660
735
  "charset_unsupported",
661
736
  "encoding_unsupported",
662
737
  "gateway_failed",
663
- "key_made_for_a_cabinet",
664
738
  "key_opened_this_call",
665
739
  "malformed_body",
666
740
  "malformed_query",
@@ -669,9 +743,7 @@ export const ERROR_CODES = Object.freeze([
669
743
  "no_such_key",
670
744
  "no_such_order",
671
745
  "no_such_route",
672
- "not_a_cabinet_key",
673
746
  "not_authorised",
674
- "not_invited",
675
747
  "not_selling",
676
748
  "not_this_purchase",
677
749
  "order_closed_before_it_was_priced",
@@ -680,10 +752,8 @@ export const ERROR_CODES = Object.freeze([
680
752
  "payment_already_spent",
681
753
  "payment_not_taken",
682
754
  "payment_not_verified",
683
- "wallet_change_not_announced",
684
- "wallet_change_nobody_to_tell",
685
- "wallet_change_raced",
686
- "wallet_change_unconfirmed",
755
+ "ship_to_changed",
756
+ "ship_to_does_not_fit",
687
757
  ]);
688
758
  /**
689
759
  * Every call of the surface, under the name it is known by in both programs.
@@ -718,7 +788,7 @@ export const API_ROUTES = Object.freeze({
718
788
  method: "POST",
719
789
  path: "/v0/catalog/publish",
720
790
  auth: "merchant_key",
721
- description: "Publishes one product, or says what is wrong with the card. Republishing under the same merchant_item_id is how a card is changed rather than how a second one appears. The catalog identifier comes back beside ok, and from then on it is what an agent, a purchase and a receipt all use. A card that is not published comes back as ok:false under the code card_rejected, never retryable, with every finding named in the error's problems. Three findings this can come back with are not about the card at all. A merchant who has not set the name their products are sold under gets no_seller_name, and the way past it is POST /v0/seller-name: a card published without one reaches a buyer's agent inside a payment request that names no seller. On a deployment that settles on a real chain, a merchant who has set no wallet gets no_payout_wallet, and the way past it is the Settings screen of the merchant's cabinet, where a person signed in sets one: the money from that card's sales is paid to the merchant's own address directly, and without one there is nowhere for it to go. On the live deployment, a merchant the operator has not admitted once gets no_operator_approval. That decision has no merchant API: test publication remains available, while live publication remains closed until the operator grants it. Every applicable finding comes back in the same problems list as whatever is wrong with the card, so one answer carries everything standing between this card and the catalog.",
791
+ description: "Publishes one product, or says what is wrong with the card. Republishing under the same merchant_item_id is how a card is changed rather than how a second one appears. The catalog identifier comes back beside ok, and from then on it is what an agent, a purchase and a receipt all use. A card that is not published comes back as ok:false under the code card_rejected, never retryable, with every finding named in the error's problems. Three findings this can come back with are not about the card at all. A merchant who has not set the name their products are sold under gets no_seller_name, and the way past it is POST /v0/seller-name: a card published without one reaches a buyer's agent inside a payment request that names no seller. On a deployment that settles on a real chain, a merchant who has set no wallet gets no_payout_wallet, and the way past it is the Settings screen of the merchant's dashboard, where a person signed in sets one: the money from that card's sales is paid to the merchant's own address directly, and without one there is nowhere for it to go. On the live deployment, a merchant the operator has not admitted once gets no_operator_approval. That decision has no merchant API: test publication remains available, while live publication remains closed until the operator grants it. Every applicable finding comes back in the same problems list as whatever is wrong with the card, so one answer carries everything standing between this card and the catalog.",
722
792
  request: CardSchema,
723
793
  response: { document: PublishResultSchema },
724
794
  },
@@ -757,14 +827,6 @@ export const API_ROUTES = Object.freeze({
757
827
  description: "Starts selling again. Cards paused in their own right stay paused: stopping all selling did not forget which they were, and putting them all back on sale would sell products their merchant took off. The answer is the whole catalog, so which cards actually came back is a fact rather than an inference. A merchant who has left is refused: leaving closed the orders that were open and left refunds owed, and this switch unwinds none of it, so a departure is not undone here.",
758
828
  response: { document: MerchantCardListSchema },
759
829
  },
760
- register_merchant: {
761
- method: "POST",
762
- path: "/v0/merchants",
763
- auth: "none",
764
- description: "Makes a merchant and the key whoever registered them will call as them with — both of those or neither. This call is not on the public origin: only the cabinet makes it, from inside the stack, when a signed-in person presses its one control, and from outside the path answers as one the site does not have. It takes no key because nobody registering has one yet; what stands in the door instead is an invitation code out of the gateway's own configuration, and a gateway with no code configured refuses every registration in the same words a wrong code gets, so this call is not a way of finding out whether registration is open. The key comes back once and is readable nowhere afterwards, and it is a key made for a cabinet: it is in no list of the merchant's keys and is not disabled through them, so no row for it comes back here — only the key itself. What the new merchant does not have is a name: they are listed under nothing until somebody sets one at POST /v0/seller-name, and until then publishing a card is refused, so a cabinet that registers a person and takes them straight to a publish screen has built a dead end. Nothing about an account, an address or a password reaches this call either: those belong to whatever signs a person in, on the other side of it. Unlike every other call on this surface that writes something, a repeat of this one is not safe and there is nothing here that could make it so: two calls make two merchants, and the caller holds a key to only the second. A caller whose connection drops before the answer arrives cannot find out from here whether the first landed — it has no key with which to ask — and the merchant it may have made cannot be swept away afterwards, because a merchant is what every card, order and receipt is owned by.",
765
- request: RegistrationRequestSchema,
766
- response: { document: RegisteredMerchantSchema },
767
- },
768
830
  get_seller_name: {
769
831
  method: "GET",
770
832
  path: "/v0/seller-name",
@@ -784,29 +846,21 @@ export const API_ROUTES = Object.freeze({
784
846
  method: "GET",
785
847
  path: "/v0/payout-wallet",
786
848
  auth: "merchant_key",
787
- description: "The address the sales of the merchant this call's own key belongs to are paid into now, and any change of it that is waiting. Payments here are not held by anybody on the way: a buyer's agent pays this address directly and no balance of the merchant's is ever held on our side, which is why the address has to be theirs and why this call exists. Any key of the merchant's reads it, the keys made for their own code included; only the merchant's cabinet sets it. Null is the ordinary answer for a merchant who has set none, and it is an answer rather than a refusal — a merchant with no wallet exists and has a settings screen to draw. The address comes back in the mixed-case spelling a wallet shows, whichever of the two accepted spellings was sent to set it, so a screen showing it shows what the merchant copied out of their wallet character for character. On the live deployment a replacement for an address already set takes effect forty-eight hours after it was announced to the merchant, and until then this answers with the address still paid and names the waiting one, with the moment it takes effect, under pending. Null there means nothing is waiting, which is always the answer on the test channel and in a sandbox.",
788
- response: { document: PayoutWalletSchema },
789
- },
790
- set_payout_wallet: {
791
- method: "POST",
792
- path: "/v0/payout-wallet",
793
- auth: "merchant_key",
794
- description: "Sets the address the sales of the merchant this call's own key belongs to are paid into. Only the merchant's cabinet makes this call, with the key made for it, from inside the stack, when a person sets the wallet on its Settings screen: keys operate the shop, and where the shop's money goes is set through the cabinet. The public origin does not route this call at all, so from outside it is a path the site does not have; at the gateway a key made for the merchant's own code is refused under not_a_cabinet_key, for the first address as for any other, and nothing is written or announced. The first address a merchant sets applies at once; on the live deployment every cabinet account of the merchant is then told of it, and a message that cannot be sent refuses nothing. On the live deployment a different address after that does not: before anything is written, every account that names the merchant is sent a message saying what changes, when, and that it was asked for in the cabinet, and the change takes effect forty-eight hours after those messages were handed to the mail provider. Until then every payment request names the address that applies now, and the answer carries the waiting one under pending with the moment it takes effect. Asking again for the address already waiting changes nothing, sends no second message and restarts no clock, and answers with the same pending change, so a retry after a dropped connection is safe; a different address replaces the waiting one and starts the forty-eight hours again; asking for the address that applies now cancels the waiting change. On the test channel and in a sandbox every change applies at once and no message is sent. The answer is the wallet as it now stands, read back from what was written rather than echoed, in the mixed-case spelling a wallet shows. Setting the address that already applies, with nothing waiting, changes nothing and answers the same way. An address whose capital letters do not agree with the rest of it is refused and nothing is written, because those capitals are a checksum and letters that disagree mean a character is wrong — and an address that is wrong is another perfectly good address belonging to somebody else. What this call will not do is take an address away: null is refused, because without an address there is nowhere to send the money and every published card would quietly come off sale — ending the selling under the name of editing a setting; somebody reaching for that wants either a different address, which is this same call, or an end to selling, which is the pause. On a deployment that settles on a real chain, a merchant with no wallet set here cannot publish a card, and the refusal at the publish says so.",
795
- request: PayoutWalletRequestSchema,
849
+ description: "The address the sales of the merchant this call's own key belongs to are paid into now, and any change of it that is waiting. Payments here are not held by anybody on the way: a buyer's agent pays this address directly and no balance of the merchant's is ever held on our side, which is why the address has to be theirs and why this call exists. Any key of the merchant's reads it; none sets it. A person signed in to the merchant's dashboard sets it on its Settings screen. On the live deployment a first address applies at once and is announced afterwards, and a replacement is told to every account of the merchant first and waits. Null is the ordinary answer for a merchant who has set none, and it is an answer rather than a refusal — a merchant with no wallet exists and has a settings screen to draw. The address comes back in the mixed-case spelling a wallet shows, whichever of the two accepted spellings was sent to set it, so a screen showing it shows what the merchant copied out of their wallet character for character. On the live deployment a replacement for an address already set takes effect forty-eight hours after it was announced to the merchant, and until then this answers with the address still paid and names the waiting one, with the moment it takes effect, under pending. Null there means nothing is waiting, which is always the answer on the test channel and in a sandbox.",
796
850
  response: { document: PayoutWalletSchema },
797
851
  },
798
852
  list_keys: {
799
853
  method: "GET",
800
854
  path: "/v0/keys",
801
855
  auth: "merchant_key",
802
- description: "The keys the merchant this call's own key belongs to made for their own code, the revoked ones among them, and never the keys themselves. A merchant who has only ever signed into a cabinet has none of these, and an empty list is that answer rather than a fault: the key a cabinet calls with is made for the cabinet and is in no list here, because the merchant did not issue it and cannot disable it. Whether the list is all of the rest is not something this document claims either: paging is not designed, and the absence of a field about it is not a promise that there is no more. The answer also names the key the call was made with, as this_call, and that field is the reason this is not a bare list: a merchant cannot disable the key their own call was made with, so a screen drawn without knowing which key that is would offer a button the gateway refuses. That identifier is not among the keys listed when the caller is a cabinet, and a client that looked it up among the rows has to be built for finding none. Every row also says when a call was last seen on that key, which is the field a screen offering to revoke one is worth drawing at all — and it is a recorded answer rather than an exact one, lagging by minutes, and blank both for a key nothing has called and for one made before this gateway began recording, which nothing here tells apart. This call is itself one of those calls, so the key it was made with reads as used at about this moment, whether or not it appears in the list.",
856
+ description: "The keys the merchant this call's own key belongs to made for their own code, the revoked ones among them, and never the keys themselves. A merchant who has only ever signed into a dashboard has none of these, and an empty list is that answer rather than a fault: the dashboard calls with no key. Whether the list is all of the rest is not something this document claims either: paging is not designed, and the absence of a field about it is not a promise that there is no more. The answer also names the key the call was made with, as this_call, and that field is the reason this is not a bare list: a merchant cannot disable the key their own call was made with, so a screen drawn without knowing which key that is would offer a button the gateway refuses. It is always one of the keys listed, since every key is one the merchant issued. Every row also says when a call was last seen on that key, which is the field a screen offering to revoke one is worth drawing at all — and it is a recorded answer rather than an exact one, lagging by minutes, and blank both for a key nothing has called and for one made before this gateway began recording, which nothing here tells apart. This call is itself one of those calls, so the key it was made with reads as used at about this moment.",
803
857
  response: { document: MerchantKeyListSchema },
804
858
  },
805
859
  issue_key: {
806
860
  method: "POST",
807
861
  path: "/v0/keys",
808
862
  auth: "merchant_key",
809
- description: "Issues another key for the merchant's own code, to the merchant this call's own key belongs to. The key is generated here and never taken from the caller, and it comes back exactly once — what is kept afterwards is a digest, so nothing can show it again. A merchant with several keys can hand one to each worker and revoke one without touching the others, which is the whole reason a key is a row. This call cannot make the other kind of key: what a cabinet calls with is asked for at POST /v0/keys/cabinet, and a key made here is one the merchant sees, names and revokes.",
863
+ description: "Issues another key for the merchant's own code, to the merchant this call's own key belongs to. The key is generated here and never taken from the caller, and it comes back exactly once — what is kept afterwards is a digest, so nothing can show it again. A merchant with several keys can hand one to each worker and revoke one without touching the others, which is the whole reason a key is a row. The merchant's dashboard issues keys the same way, and every key a merchant has is one they see, name and revoke.",
810
864
  request: IssueKeyRequestSchema,
811
865
  response: { document: IssuedKeySchema },
812
866
  },
@@ -814,23 +868,9 @@ export const API_ROUTES = Object.freeze({
814
868
  method: "POST",
815
869
  path: "/v0/keys/:key_id/disable",
816
870
  auth: "merchant_key",
817
- description: "Stops one of this merchant's keys working, from that instant, and touches no other key. Disabling a key that is already disabled changes nothing and answers the same way, keeping the instant it was first revoked at, so a retry after a dropped connection is safe. Three refusals are worth knowing before a screen is built on this. A key belonging to another merchant is answered exactly as a key that does not exist, so this call is not a way of counting somebody else's keys. A key made for a cabinet is refused under key_made_for_a_cabinet, whoever asks and however they came by its identifier: this call reaches the keys a merchant issued for their own code and nothing else, and replacing the one a cabinet holds is POST /v0/keys/cabinet and the forgetting beside it. And the key this call was made with cannot be disabled by it — that one click and no more: the refusal is about the key in front of it, so a merchant holding two keys of their own can still disable either with the other, and two such calls at one moment can leave them with none of their own. That is not refused here or anywhere, and what it costs is their own code going quiet rather than the way back in, which is a key of the other kind.",
871
+ description: "Stops one of this merchant's keys working, from that instant, and touches no other key. Disabling a key that is already disabled changes nothing and answers the same way, keeping the instant it was first revoked at, so a retry after a dropped connection is safe. Two refusals are worth knowing before a screen is built on this. A key belonging to another merchant is answered exactly as a key that does not exist, so this call is not a way of counting somebody else's keys. And the key this call was made with cannot be disabled by it — that one click and no more: the refusal is about the key in front of it, so a merchant holding two keys of their own can still disable either with the other, and two such calls at one moment can leave them with none of their own. That is not refused here or anywhere, and what it costs is their own code going quiet rather than the way back in, which is signing in to the dashboard with the link mailed to them.",
818
872
  response: { document: DisabledKeySchema },
819
873
  },
820
- issue_cabinet_key: {
821
- method: "POST",
822
- path: "/v0/keys/cabinet",
823
- auth: "merchant_key",
824
- description: "Makes a key for a cabinet to call as this merchant with, and hands it back once. This call and the forgetting beside it are not on the public origin: only the cabinet makes them, from inside the stack, and from outside the path answers as one the site does not have. It is a key of a different kind from the ones at /v0/keys: the merchant did not ask for it, never sees it, and it appears in no list of theirs — so nothing comes back but the key itself. What this is for is a cabinet that replaces its own credential every time somebody signs in, which is what keeps a copy of a cabinet's database from being a set of working keys for long. The call is refused to a key made for the merchant's own code, under not_a_cabinet_key: these two calls are the cabinet's own, and a merchant asking for one would be asking for a credential to a cabinet they are not standing in.",
825
- response: { document: CabinetKeySchema },
826
- },
827
- forget_cabinet_key: {
828
- method: "DELETE",
829
- path: "/v0/keys/cabinet",
830
- auth: "merchant_key",
831
- description: "Removes the key this call was made with, and no other. Like the call that makes such a key, it is not on the public origin: only the cabinet makes it, from inside the stack. It is removed rather than revoked: a merchant never issued one, never sees one and would never read a revoked one back, so a row kept for the history would be history for nobody. There are no parameters, and that is not a convenience — a key belonging to anybody else, this merchant included, is unreachable here by construction, because the only way to name a key is to be holding it. What a caller does with this is put a key of its own beyond use once it has stopped signing in with it: ask for a fresh key, write it down where the account is kept, then forget the one that was there. A caller that forgets first can be left naming a key that no longer exists, which is the one order that locks somebody out. Made a second time with the same key it is refused as a key that does not exist, which is safe and is the confirmation the first one landed: either way that key is gone and nothing else has moved. It is refused to a key made for the merchant's own code, under not_a_cabinet_key: those are revoked from the list they appear on, at an instant their owner can read back, and removing the row outright would take that history away.",
832
- response: { document: ForgottenCabinetKeySchema },
833
- },
834
874
  get_order: {
835
875
  method: "GET",
836
876
  path: "/v0/orders/:order_id",
@@ -865,7 +905,7 @@ export const API_ROUTES = Object.freeze({
865
905
  method: "POST",
866
906
  path: "/v0/orders/:order_id/answer",
867
907
  auth: "merchant_key",
868
- description: "What the merchant's handler returned for an order it was given: the goods, a refusal, or an acceptance. The SDK sends this itself, in every mode, and it is the only way a synchronous answer reaches us at all — there the handler's return is the delivery and the refusal, and the explicit deliver and refuse calls do not apply. An acceptance is answered with the word accepted — the order is taken on, and the goods follow through the deliver call — or with already_delivered when the order closed before the acceptance reached us, which a redelivered order makes ordinary rather than a fault. A synchronous answer that arrives after its deadline is not an error: the work exists and a repeat purchase collects it, and the answer says so in the word purchase_already_closed.",
908
+ description: "What the merchant's handler returned for an order it was given: the goods, a refusal, or an acceptance. The SDK sends this itself, in every mode, and it is the only way a synchronous answer reaches us at all — there the handler's return is the delivery and the refusal, and the explicit deliver and refuse calls do not apply. An acceptance is answered with the word accepted — the order is taken on, and the goods follow through the deliver call — or with already_delivered when the order closed before the acceptance reached us, which a redelivered order makes ordinary rather than a fault. A synchronous order still waiting for its goods cannot be taken on, because the deliver call that would carry them later does not exist in that mode: the acceptance is refused with not_applicable_in_mode, the order is not sent to the handler again for it, and it closes on its deadline with nothing charged. A synchronous answer that arrives after its deadline is not an error: the work exists and a repeat purchase collects it, and the answer says so in the word purchase_already_closed.",
869
909
  request: HandlerAnswerSchema,
870
910
  response: { document: OrderCallResponseSchema },
871
911
  },
@@ -873,7 +913,7 @@ export const API_ROUTES = Object.freeze({
873
913
  method: "POST",
874
914
  path: "/v0/orders/:order_id/deliver",
875
915
  auth: "merchant_key",
876
- description: "The goods for an order the merchant took on earlier — the asynchronous mode's closure verb, called by the merchant rather than by the SDK. Idempotent by the order's identifier: called again after a dropped connection it delivers nothing twice and charges nothing twice, so repeating it is safe and keeping a note of what was already sent is not needed. A late call is accepted too — where the delivery deadline has passed and the refund has not yet been paid out, delivering closes the debt.",
916
+ description: "The goods for an order the merchant took on earlier — the asynchronous mode's closure verb, called by the merchant rather than by the SDK. Idempotent by the order's identifier: called again after a dropped connection it delivers nothing twice and charges nothing twice, so repeating it is safe and keeping a note of what was already sent is not needed. A late call is accepted too — where the delivery deadline has passed and the refund has not yet been paid out, delivering closes the debt. On a parcel's order the body is its shipment instead of goods (the shipment document: a carrier, a tracking number or null, and optionally a tracking page and an expected window). Agentify records the instant the call arrived as when the parcel shipped, the order reads shipped, and the shipment cannot be changed afterwards: the same one sent again succeeds, and a different one is refused with shipment_already_recorded.",
877
917
  request: DeliverySchema,
878
918
  response: { document: OrderCallResponseSchema },
879
919
  },
@@ -889,7 +929,7 @@ export const API_ROUTES = Object.freeze({
889
929
  method: "POST",
890
930
  path: "/v0/orders/:order_id/accept",
891
931
  auth: "merchant_key",
892
- description: "Takes an order on: the merchant will deliver, and says how long they expect it to take when they know. An empty body is a complete answer. The same order is taken on again every time it is redelivered, and the success carries no word: taking on an order that is already delivered succeeds here too, and this route does not tell the two apart. The answer route does, because it carries whichever of the three things a handler returned.",
932
+ description: "Takes an order on: the merchant will deliver, and says how long they expect it to take when they know. An empty body is a complete answer. A synchronous order still waiting for its goods cannot be taken on, because its goods travel only in the handler's answer: the call is refused with not_applicable_in_mode. The same order is taken on again every time it is redelivered, and the success carries no word: taking on an order that is already delivered succeeds here too, and this route does not tell the two apart. The answer route does, because it carries whichever of the three things a handler returned.",
893
933
  request: AcceptanceSchema,
894
934
  response: { document: OrderAcceptResponseSchema },
895
935
  },
@@ -921,7 +961,7 @@ export const API_ROUTES = Object.freeze({
921
961
  method: "GET",
922
962
  path: "/x402/orders/:order_id/status",
923
963
  auth: "order_id",
924
- description: "What became of a purchase, for the agent that made it: where the order stands, what it sold for, and the goods once they are the buyer's. It is the route an agent that bought a product whose goods come later collects them on, and without it half a catalogue takes money and hands back an order nobody can act on. Every answer that carries the order document names this address in status_url, whole and ready to call, the purchase's own answer included. An order whose goods come later is owed them by the delivery deadline its card carried when the order was opened, counted from its payment: fulfill_deadline_seconds where the card names one, and a day where it names none. An order still without goods at that deadline becomes refund_due, and a paid order the merchant refuses or leaves behind becomes it sooner: the merchant owes the buyer the goods or the money back. That is not an ending, and it carries no further deadline. Goods the merchant delivers after it still appear here and settle the debt, and the order becomes delivered. Nor does refund_due say whether the money has come back: the merchant returns it from their own wallet, the gateway has no way yet to record that they did, and until it has one no order moves on to refunded — a buyer already paid back still reads refund_due here. So an agent holding refund_due can still collect goods here, nothing here tells it when to stop asking, and its own wallet is where a refund shows. Knowing the order's identifier is the proof (ADR-0011), so this call takes no key: an agent has no account and no registration, and the identifier is handed to exactly one party. Two things follow for whoever mounts it. Which door a call is behind is read off auth and never off the address, whatever the prefixes happen to agree on today. And an identifier that names no order must be answered exactly as any other unknown one is, or the refusal becomes a way of counting the orders behind it.",
964
+ description: "What became of a purchase, for the agent that made it: where the order stands, what it sold for, and the goods once they are the buyer's. It is the route an agent that bought a product whose goods come later collects them on, and without it half a catalogue takes money and hands back an order nobody can act on. Every answer that carries the order document names this address in status_url, whole and ready to call, the purchase's own answer included. An order whose goods come later is owed them by the delivery deadline its card carried when the order was opened, counted from its payment: fulfill_deadline_seconds where the card names one, and a day where it names none. An order still without goods at that deadline becomes refund_due, and a paid order the merchant refuses or leaves behind becomes it sooner: the merchant owes the buyer the goods or the money back. That is not an ending, and it carries no further deadline. Goods the merchant delivers after it still appear here and settle the debt, and the order becomes delivered. Nor does refund_due say whether the money has come back: the merchant returns it from their own wallet, the gateway has no way yet to record that they did, and until it has one no order moves on to refunded — a buyer already paid back still reads refund_due here. So an agent holding refund_due can still collect goods here, nothing here tells it when to stop asking, and its own wallet is where a refund shows. Knowing the order's identifier is the proof (ADR-0011), so this call takes no key: an agent has no account and no registration, and the identifier is handed only to the parties to the sale — the agent that bought, its merchant and Agentify — and appears in no catalog or listing. Two things follow for whoever mounts it. Which door a call is behind is read off auth and never off the address, whatever the prefixes happen to agree on today. And an identifier that names no order must be answered exactly as any other unknown one is, or the refusal becomes a way of counting the orders behind it.",
925
965
  response: { document: AgentOrderStatusSchema },
926
966
  },
927
967
  });