Files and proofs

File handling has two integrator-facing paths:

  1. Generic transaction files — upload URL → client upload → confirm → signed reads under /v1/txs/{txId}/files*
  2. OTC proofs — prepare proof → upload → submit → counterparties verify/deny

Limits#

RuleValue
Extensions.pdf, .png, .jpg, .jpeg, .gif, .bmp, .webp
Not allowedTIFF (.tif / .tiff)
Generic tx max size5 MB per file at confirmation
File name patternAlphanumeric, spaces, hyphens, dots + valid extension

Generic transaction files#

Prerequisites#

  • Conditional auth with resolved profile context
  • Permission to attach files to the transaction (canAddFilesToTransaction)

1. Request upload URLs#

curl -sS https://api-stg.vudy.app/v1/txs/TX_ID/files/upload-url \
  -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 '{"fileNames":["invoice.pdf","receipt.png"]}'
const { data } = await fetch(
	`https://api-stg.vudy.app/v1/txs/${txId}/files/upload-url`,
	{
		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({ fileNames: ["invoice.pdf"] }),
	},
).then((r) => r.json());
// { uploadId, uploadUrls }

2. Upload bytes to each signed URL#

Use each returned uploadUrls[] entry. Upload the bytes to its signedUrl before expiresAt, and preserve its filePath for confirmation:

curl -sS -X PUT "SIGNED_UPLOAD_URL" \
  -H "Content-Type: application/pdf" \
  --data-binary @invoice.pdf

3. Confirm#

curl -sS https://api-stg.vudy.app/v1/txs/TX_ID/files \
  -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 '{
    "uploadId": "UPLOAD_ID",
    "filePaths": ["path/from/upload-url"],
    "fileNames": ["invoice.pdf"]
  }'

filePaths and fileNames must be the same length.

4. Read signed URLs#

curl -sS https://api-stg.vudy.app/v1/txs/TX_ID/files \
  -H "x-api-key: vudy_sandbox_YOUR_KEY" \
  -H "x-profile-id: PROFILE_UUID" \
  -H "x-team-id: TEAM_UUID"

OTC proofs#

Used during OTC proofRequired / proofing / verifying phases. See OTC requester.

Prepare#

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":["wire-receipt.pdf"],"splitIndex":0}'
const { data } = await fetch(
	`https://api-stg.vudy.app/channel/vudy/otc/${otcRequestId}/prepare-proof`,
	{
		method: "POST",
		headers: {
			"x-api-key": process.env.VUDY_API_KEY,
			"Content-Type": "application/json",
		},
		body: JSON.stringify({ fileNames: ["wire-receipt.pdf"], splitIndex: 0 }),
	},
).then((r) => r.json());
// { uploadUrls, uploadId, splitIndex }

Upload, generate a split signature, then submit#

Upload each file using the returned signed URL. Generate the approval signature with the session of the party identified by the status steps:

curl -sS https://api-stg.vudy.app/v1/otc/signatures/approve-split \
  -H "x-api-key: vudy_sandbox_YOUR_KEY" \
  -H "Authorization: Bearer SESSION_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "otcRequestId": "OTC_REQUEST_ID",
    "splitIndex": 0,
    "userWalletAddress": "0xSIGNER"
  }'

For a managed wallet, use returned data.signature. If typed data is returned, sign it with userWalletAddress.

curl -sS -X POST \
  https://api-stg.vudy.app/channel/vudy/otc/OTC_REQUEST_ID/submit-proof \
  -H "x-api-key: vudy_sandbox_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "splitIndex": 0,
    "signature": "0x..."
  }'

Verify or deny#

The counterparty uses approve-split to generate the verify signature. To deny, first generate a deny signature:

curl -sS https://api-stg.vudy.app/v1/otc/signatures/deny-split \
  -H "x-api-key: vudy_sandbox_YOUR_KEY" \
  -H "Authorization: Bearer SESSION_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "otcRequestId": "OTC_REQUEST_ID",
    "splitIndex": 0,
    "userWalletAddress": "0xSIGNER"
  }'
# Verify
curl -sS -X POST \
  https://api-stg.vudy.app/channel/vudy/otc/OTC_REQUEST_ID/verify-proof \
  -H "x-api-key: vudy_sandbox_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"splitIndex":0,"signature":"0x..."}'

# Deny
curl -sS -X POST \
  https://api-stg.vudy.app/channel/vudy/otc/OTC_REQUEST_ID/deny-proof \
  -H "x-api-key: vudy_sandbox_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "splitIndex": 0,
    "walletAddress": "0x...",
    "signature": "0x...",
    "reason": "Amount mismatch"
  }'

Denial moves the OTC request toward conflict. Successful verify advances toward completed.

Next steps#