OTC requester flows

OTC requester flows combine gating checks, request creation, offer acceptance, escrow signatures, and proof exchange. Prefer the Vudy OTC capability for creation; use the dedicated requester endpoints for the follow-up actions returned by status.

Prerequisites#

  • Session or header profile context
  • Passing OTC gating (canCreate: true)
  • Team bank account (userBankId) and tax info as required
  • KYC or KYB approved for the acting profile/team
  • Compliance fee paid when gating reports missing.complianceFee

Flow overview#

  1. Check OTC gating
  2. Create an OTC request
  3. Poll for offers
  4. Accept or reject an offer
  5. Complete escrow / proof steps
  6. Poll until completed, cancelled, or conflict

1. Check gating#

curl -sS https://api-stg.vudy.app/v1/otc/gating \
  -H "x-api-key: vudy_sandbox_YOUR_KEY" \
  -H "x-profile-id: PROFILE_UUID" \
  -H "x-team-id: TEAM_UUID"
const { data } = await fetch("https://api-stg.vudy.app/v1/otc/gating", {
	headers: {
		"x-api-key": process.env.VUDY_API_KEY,
		"x-profile-id": profileId,
		"x-team-id": teamId,
		// or Authorization: Bearer <session>
	},
}).then((r) => r.json());

// {
//   canCreate, amountAvailableWithoutFee, gate,
//   missing: { kyc?, kyb?, taxInfo?, bank?, complianceFee? },
//   warnings?
// }

Resolve each missing item via KYC/KYB, team banks/tax info, or compliance checkout before create.

2. Validate and create an OTC request#

Preferred: discover a Vudy OTC capability, validate every parameter marked signedInput, then POST /channels/quote and POST /channels/execute (session required for execute). For example, validate userBankId when discovery requires it:

curl -sS https://api-stg.vudy.app/channels/vudy/validate \
  -H "x-api-key: vudy_sandbox_YOUR_KEY" \
  -H "x-profile-id: PROFILE_UUID" \
  -H "x-team-id: TEAM_UUID" \
  -H "Content-Type: application/json" \
  -d '{
    "paramKey": "userBankId",
    "value": "BANK_UUID",
    "capabilityId": "CAPABILITY_UUID"
  }'

Pass the result to execute as "userBankId": ["BANK_UUID", "SIGNATURE"]. Validate taxId the same way when it is present and marked as signed. See Channel payments for quote/execute examples.

The provider-specific create route below is retained for integrations using the classic Vudy payload:

curl -sS https://api-stg.vudy.app/channel/vudy/otc/create \
  -H "x-api-key: vudy_sandbox_YOUR_KEY" \
  -H "x-profile-id: PROFILE_UUID" \
  -H "x-team-id: TEAM_UUID" \
  -H "Content-Type: application/json" \
  -d '{
    "targetAddress": "0xRECEIVER",
    "amount": 1000,
    "channelParams": {
      "requestType": "buy",
      "amountType": "buy",
      "buyCurrency": "USDC",
      "sellCurrency": "USD",
      "buyChain": "ethereum",
      "sellChain": "ethereum",
      "userBankId": "BANK_UUID",
      "taxId": "AB-123"
    }
  }'
const { data } = await fetch(
	"https://api-stg.vudy.app/channel/vudy/otc/create",
	{
		method: "POST",
		headers: {
			"x-api-key": process.env.VUDY_API_KEY,
			"x-profile-id": profileId,
			"x-team-id": teamId,
			"Content-Type": "application/json",
		},
		body: JSON.stringify({
			targetAddress: receiverAddress,
			amount: 1000,
			channelParams: {
				requestType: "buy", // or "sell"
				amountType: "buy", // which side `amount` refers to
				buyCurrency: "USDC",
				sellCurrency: "USD",
				buyChain: "ethereum",
				sellChain: "ethereum",
				userBankId: bankUuid,
				taxId: "AB-123", // optional; alphanumeric + dashes
			},
		}),
	},
).then((r) => r.json());

// Prefer capability execute for new integrations; response includes
// { txId, channelTableType: "ch_vudy_otc_request", recordId, walletFlow }
// Provider-specific create returns the OTC request record (use its id as OTC_REQUEST_ID).

3. Poll for offers#

curl -sS https://api-stg.vudy.app/channels/vudy/TX_OR_RECORD_ID \
  -H "x-api-key: vudy_sandbox_YOUR_KEY"

Response includes detail (OTC status), offers, actionNeeded, and steps. Use the recordId returned by capability execute as the OTC request id for requester actions.

4. Accept or reject an offer#

Generate the requester’s escrow signature with the active session:

curl -sS https://api-stg.vudy.app/v1/otc/signatures/create-escrow/user \
  -H "x-api-key: vudy_sandbox_YOUR_KEY" \
  -H "Authorization: Bearer SESSION_JWT" \
  -H "Content-Type: application/json" \
  -d '{"otcRequestId":"OTC_REQUEST_ID","offerId":"OFFER_ID"}'

For a managed wallet, use returned data.signature. If the response instead contains typed data, sign it with the request wallet. Then call the exact action URL returned by status:

# Accept
curl -sS -X POST \
  https://api-stg.vudy.app/channels/vudy/action/accept_offer/OTC_REQUEST_ID \
  -H "x-api-key: vudy_sandbox_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"offerId":"OFFER_ID","userSignature":"0x..."}'

# Reject
curl -sS -X POST \
  https://api-stg.vudy.app/channels/vudy/action/reject_offer/OTC_REQUEST_ID \
  -H "x-api-key: vudy_sandbox_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"offerId":"OFFER_ID"}'
await fetch(
	`https://api-stg.vudy.app/channels/vudy/action/accept_offer/${otcRequestId}`,
	{
		method: "POST",
		headers: {
			"x-api-key": process.env.VUDY_API_KEY,
			"Content-Type": "application/json",
		},
		body: JSON.stringify({ offerId, userSignature }),
	},
);

Offer statuses: newaccepted | rejected | expired.

5. Proof steps#

When status reaches proof phases, follow the role-specific steps:

  • Buy request: requester submits proof; accepted provider verifies or denies it.
  • Sell request: accepted provider submits proof; requester verifies or denies it.

Dedicated requester routes are:

StepPath
Prepare uploadPOST /channel/vudy/otc/{id}/prepare-proof
Submit proofPOST /channel/vudy/otc/{id}/submit-proof
Verify proofPOST /channel/vudy/otc/{id}/verify-proof
Deny proofPOST /channel/vudy/otc/{id}/deny-proof
curl -sS -X POST \
  https://api-stg.vudy.app/channel/vudy/otc/OTC_REQUEST_ID/prepare-proof \
  -H "x-api-key: vudy_sandbox_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"fileNames":["receipt.pdf"],"splitIndex":0}'

Upload files to the returned signed URLs, generate the split signature, then submit. See Files and proofs.

6. Cancel#

curl -sS -X POST \
  "https://api-stg.vudy.app/channels/vudy/action/cancel_tx/OTC_REQUEST_ID" \
  -H "x-api-key: vudy_sandbox_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"requestId":"OTC_REQUEST_ID"}'

Cancelling OTC sets detail status cancelled and parent tx canceled (US spelling on parent).

Status transitions#

OTC request detail:

newofferReceivedprocessingproofRequiredproofingproofSubmittedverifyingcompleted

Terminal / exception: cancelled, conflict.

Next steps#