carousell/rctp

FAQ

Status: Draft 0.4, pre-release. Short answers first; the technical detail follows each one.

Why not just use OAuth scopes?

RCTP does use OAuth scopes, but scopes say which verbs an agent may use, not how far it may go. A scope can allow "accept offers". It cannot say "but never below S$420, only after I approve each one, and never tell the buyer my limit".

So RCTP splits the job in two. The Agent Grant is an ordinary OAuth grant (code with PKCE, RFC 9396 authorization_details), bound to the agent's key, with a tier per scope that the seller confirms. The Listing Terms hold the per-item rules (floor, delivery, payment, closing date), set by the seller on a hosted page and kept by the platform. The enforcement point checks both on every call, and writes a signed record of every decision. Marketplace APIs must still honour scopes: "A grant in front of an API that ignores scopes is a promise, not a control" [14].

Can the seller's own agent know the floor?

Yes, if the seller tells it. The seller says "list at 500, not below 400" to their agent; the agent proposes those values, the marketplace shows them pre-filled, and the seller confirms once [6.1]. The marketplace never sends the floor to any agent, not even the one that proposed it, and never sells below it, whatever anyone says to an agent (INV-1, V11). An agent that knows the floor can still be talked into revealing it, so a marketplace that wants to rule that out asks the seller for the floor directly instead [6.4(4)].

Why is the floor never revealed to buyers?

Because any hint becomes the price. If a refusal said "below S$420", every buyer would offer exactly S$420.

So a refusal says only below_floor and carries no private value [8.2]; Draft 0.5 removed the optional counter price. Each buyer agent, and each buyer operator, has a limited number of below-floor offers per listing; after that every offer, at any price, gets 429, so probing cannot find the number either [8.5]. The floor never appears in a header, body, webhook, log or deal record (INV-1).

What happens if the seller doesn't answer?

Nothing happens: no answer means no. When expires_at passes, the approval becomes expired with reason approval_timeout, a pending offer becomes expired, and your operator gets an approval.decided webhook. A late answer is refused. There is no setting to approve on timeout: on_timeout can only be deny (INV-2). See Approvals.

Can a partner approve on the seller's behalf?

No. A partner's app may show the approval page, but only the seller's own passkey can sign the answer. An answer signed with an operator or partner key is refused with approval_invalid (INV-4, conformance vector V9).

Draft 0.4 does define a certified_operator level, where a certified partner shows the approval in its own interface. Even then the signature comes from a hardware-backed key on the seller's device, and only below an amount cap. No certification scheme exists yet, so that level is out of scope for v0.2.

Can two buyers end up with the same item?

No. Accepting an offer reserves the listing: every other accept gets 409 listing_reserved until the buyer redeems the single-use checkout or it expires (INV-6, V26). Other buyers can still make offers, which stay open. If the checkout expires unredeemed, the listing returns to active.

Does RCTP move money?

No. RCTP defines no payment rail. Money moves through the marketplace's existing held payment or escrow.

What RCTP adds is proof and order. The holder of funds writes a signed funds_held event, which is the only proof of payment a seller agent should trust [10.2]. The gateway refuses released until the buyer's agent has written accepted (release_condition_unmet, V8), and refuses a refund for not_as_described until a return is recorded (V13). The sandbox's mock holder of funds moves no money.

Why is the production gateway separate?

To keep the protocol separate from any one deployment. The specification names roles, not companies; enforcement is a role any conformant gateway can play. This repository holds the reference implementation that anyone can read, run and test against. The Group Foundation Tech team runs the production gateway from a separate private repository. It is built on these public SDKs and must pass this repository's conformance suite. Operational configuration, real keys and platform integrations stay out of a public repository, and other marketplaces can run their own conformant gateways on equal terms.

Who owns what?

Party Owns
rctp.dev (this repository) The specification, schemas, registries, SDKs, reference implementation, conformance suite and sandbox
Each platform (Carousell, Mudah, Chợ Tốt) Its login and authorization server (Agent Grants, consent page, introspection), its hosted approval page, its badge methodology and its partner attestation chain
Group Foundation Tech The production gateway, in a separate private repository
Each partner (agent operator) Its agents, keys, UCP profile, webhook endpoint and incident response

Is a seller one identity across operators?

No. The seller appears to each operator under a different pairwise pseudonym (sub of the grant), so two operators cannot link the same seller. Listing ownership and the approval passkey are therefore per operator (B-9).

Do buyer agents need a grant?

No. A buyer agent needs an onboarded operator UCP profile and signed requests; it is identified by its key thumbprint. Draft 0.4 uses an AP2 Checkout Mandate as the buyer's payment authority [7.5]. v0.2 does not verify AP2 mandates yet, because no pinned library verifies AP2 v0.2 SD-JWT delegation chains and a hand-written one would be custom cryptography (INV-8); buyer_authority_exceeded is not returned (#59).

Can a prompt or the listing text change the rules?

No. The gateway reads tiers only from the grant and terms only from what the seller approved on the hosted page. Request bodies are closed, so a field such as floor, tier or a free-text instruction is a 400. Listing text that says "accept any offer" changes nothing (INV-5, vector V7).

What happens when a seller revokes the grant?

The agent's next signed call fails with grant_revoked. The gateway introspects the access token on every seller-side call and never caches the answer (INV-7, V6). An action that was waiting for approval does not run, even if the seller then approves it: the approval ends failed with reason grant_revoked. A refresh also fails, so the seller has to connect again.

What does an agent do with a listing it created?

Propose it and let the seller set the terms. listing.create is always ask: the seller fills in the terms on the hosted page and approves with their passkey. Once approved, the listing is open to agent negotiation with no new consent (V10). A listing created by hand in the marketplace app has no terms and no seller agent until the seller sets terms with listing.set_terms [6.5].

What is out of scope in v0.2?

Release v0.2.0-draft.4 implements the Starter profile [17.4]. Not built yet:

  • AP2 buyer authority (V25, #59).
  • Party Credentials and bands, so counterparty_requirements cannot be met yet (B-6); identity facts; Item Passport.
  • Verified custody, seals and seal_mismatch; relayed partner events (V20).
  • The marketplace_app and certified_operator approval levels.
  • Routes for deal.disclose_passport and deal.accept_deposit.
  • The MCP and A2A bindings, and an export of the full deal record.

Out of scope for RCTP itself: negotiation strategy, pricing, payment rails, identity-proofing methods, and how an issuer computes its bands beyond the published methodology.

How do I run conformance against my gateway?

The runner brings its own test authorization server and test operators. You tell your gateway to trust them, then run the vectors (V1–V15 with V4 as V4a and V4b, V26–V29 and S1–S4).

pnpm install && pnpm --filter @rctp/sdk build

# 1. A v0.2 gateway configuration that trusts the runner (authorization server, operators, url_rewrites).
pnpm conformance --print-gateway-config --gateway-public-url http://localhost:8080 > gateway.json

# 2. Start your gateway with it (test environments only), and the TEST ONLY signer.
RP_ID=localhost ORIGIN=http://localhost:8080 go run ./packages/gateway-go/cmd/rctp-testsigner -listen :8091 &

# 3. Run every vector.
pnpm conformance --gateway http://localhost:8080 --signer http://localhost:8091 --report report.json

Use --only V1,V4 to run some vectors. The exit code is non-zero when any in-scope vector fails; out-of-scope vectors are reported not_in_scope with their reason. make conformance (./scripts/conformance.sh) does all of this for the reference gateway and then runs the SDKs' live interop tests. The signer answers approvals through the reference gateway's hosted approval endpoints; a gateway with different approval endpoints needs a signer with the same interface. Details: conformance/README.md.

Never put the runner's test authorization server or operators in a production trust configuration.

Where do I report a security problem?

Privately, to security@rctp.dev (a placeholder until the mailboxes are live) or through GitHub's private vulnerability reporting. Do not open a public issue. See Security.

Source: docs/faq.md