Overview
Proposals are merchant-created payment intents that buyers scan and settle. Each proposal contains:
- Merchant information (DID, name, public key)
- Line items and total amount
- Deliverables (receipt, warranty)
- Constraints (age gate, region restrictions)
- Intent hash for on-chain settlement verification
All proposal endpoints require API key authentication.
Endpoints
Create Proposal
Create a new commerce proposal and generate a QR code.
POST /api/identipay/v1/proposals
Requires Authorization: Bearer {apiKey} header
Request Body
Array of line items in the purchase
Quantity (positive integer)
Unit price as string (supports arbitrary precision)
Currency code (optional, defaults to proposal-level currency)
Total amount for the transaction
Currency code (e.g., “SUI”, “USDC”)
What the merchant will deliver
Whether a receipt will be provided
Optional warranty termsWarranty duration in days (positive integer)
Whether warranty can be transferred
Optional purchase constraints
Minimum age requirement (requires ZK age proof)
Array of allowed region codes
Proposal expiry in seconds (max 86400 = 24 hours)
Response
UUID for this transaction
Hash of the proposal intent (used for on-chain verification)
Data URL of QR code image (base64-encoded PNG)
Payment URI (identiPay protocol)
Full proposal object (JSON-LD CommerceProposal)
ISO 8601 timestamp when proposal expires
Example Request
curl -X POST https://api.identipay.com/api/identipay/v1/proposals \
-H "Authorization: Bearer ip_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"name": "Coffee",
"quantity": 2,
"unitPrice": "5.00"
}
],
"amount": {
"value": "10.00",
"currency": "USDC"
},
"deliverables": {
"receipt": true
},
"expiresInSeconds": 900
}'
Example Response
{
"transactionId": "550e8400-e29b-41d4-a716-446655440000",
"intentHash": "a1b2c3d4e5f6...",
"qrDataUrl": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
"uri": "identipay://acme.com/550e8400-e29b-41d4-a716-446655440000",
"expiresAt": "2026-03-09T21:15:00.000Z",
"proposal": {
"@context": "https://schema.identipay.net/v1",
"@type": "CommerceProposal",
"transactionId": "550e8400-e29b-41d4-a716-446655440000",
"merchant": {
"did": "did:identipay:acme.com:...",
"name": "Acme Store",
"suiAddress": "0xabc123...",
"publicKey": "a1b2c3d4..."
},
"items": [
{
"name": "Coffee",
"quantity": 2,
"unitPrice": "5.00"
}
],
"amount": {
"value": "10.00",
"currency": "USDC"
},
"deliverables": {
"receipt": true
},
"constraints": null,
"expiresAt": "2026-03-09T21:15:00.000Z",
"intentHash": "a1b2c3d4e5f6...",
"settlementChain": "sui",
"settlementModule": "0x...::settlement"
}
}
Error Responses
Missing or invalid API key.{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid API key"
}
}
Show 400 Validation Error
Invalid proposal input.{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid proposal input",
"details": {
"fieldErrors": {
"items": ["Array must contain at least 1 element(s)"],
"amount.value": ["Required"]
}
}
}
}
Implementation Details
Proposal Creation Flow
When a proposal is created (see routes/proposals.ts:17-68):
- Authenticate: Verify API key and load merchant info
- Validate Input: Check all required fields and constraints
- Generate Transaction ID: Create UUID for this transaction
- Build Proposal: Create full JSON-LD CommerceProposal object
- Compute Intent Hash: Hash the proposal for on-chain verification
- Generate QR Code: Create QR code image and data URL
- Store Proposal: Save to database with status “pending”
- Return Response: Return transaction ID, QR code, and full proposal
Proposal Schema (JSON-LD)
Proposals follow the CommerceProposal schema:
{
"@context": "https://schema.identipay.net/v1",
"@type": "CommerceProposal",
"transactionId": "uuid",
"merchant": {
"did": "did:identipay:...",
"name": "...",
"suiAddress": "0x...",
"publicKey": "..."
},
"items": [...],
"amount": {...},
"deliverables": {...},
"constraints": {...},
"expiresAt": "ISO 8601",
"intentHash": "...",
"settlementChain": "sui",
"settlementModule": "0x...::settlement"
}
Intent Hash
The intent hash is computed from:
- Transaction ID
- Merchant DID
- Amount and currency
- Expiry timestamp
- Constraint requirements
This hash is verified on-chain during settlement to ensure the buyer is settling the exact proposal shown.
QR codes encode a payment URI:
identipay://{merchant-hostname}/{transaction-id}
Wallets scan this QR code and resolve the full proposal via the /intents/:txId endpoint.
Proposal Lifecycle
Status Values:
pending: Awaiting payment
settled: Payment confirmed on-chain
expired: Expiry time passed
cancelled: Merchant cancelled
Automatic Expiry
A background task runs every 30 seconds to mark expired proposals:
setInterval(async () => {
await db
.update(proposals)
.set({ status: "expired" })
.where(
and(
eq(proposals.status, "pending"),
lt(proposals.expiresAt, new Date())
)
);
}, 30_000);
- Intents - Resolve proposal by transaction ID
- Transactions - Check proposal status, submit settlement
- WebSocket - Real-time proposal status updates