Managing Decisions
Learn how to capture and manage user decisions on proposals using the SDK.
Decision API
The proposals.decide() method records the user's decision on a proposal.
decide.ts
// Approve a proposal
const decision = await pf.proposals.decide(proposalId, {
action: 'approve',
});
// Reject a proposal
const decision = await pf.proposals.decide(proposalId, {
action: 'reject',
reason: 'Content needs to be more technical',
});Decision Options
options.ts
interface DecideInput {
// Required: The decision action
action: 'approve' | 'reject';
// Optional: Partial edits to apply
// For approvals: merged into finalObject
// For rejections: become constraints for regeneration
edits?: Partial<T>;
// Optional: Reason for the decision
reason?: string;
// Optional: Additional metadata
metadata?: Record<string, unknown>;
}
// Response structure
interface Decision {
id: string;
proposalId: string;
action: 'approve' | 'reject';
edits: Record<string, unknown> | null;
finalObject: Record<string, unknown> | null;
reason: string | null;
metadata: Record<string, unknown>;
createdAt: string;
}Approve with Edits
Users can make modifications before approving. Pass partial edits that get merged with the original.
edit.ts
// Fetch the proposal
const proposal = await pf.proposals.get(proposalId);
// User wants to change just the title
const decision = await pf.proposals.decide(proposalId, {
action: 'approve',
edits: {
title: 'Better Title by User',
},
});
// decision.finalObject contains the merged result
console.log(decision.finalObject);
// { title: 'Better Title by User', content: '...original...', tags: [...] }Reject with Edits
When rejecting, you can include edits that become constraints for the next regeneration. This enables iterative refinement where users lock in the parts they like.
reject-edits.ts
// User likes the recipe but wants 4 portions instead of 2
const decision = await pf.proposals.decide(proposalId, {
action: 'reject',
reason: 'Need more portions',
edits: { portions: 4 }, // Will be used as constraint
});
// Regenerate - edits automatically become constraints
const { proposal: improved } = await pf.proposals.regenerate(proposalId, {});
// The new proposal will have portions=4, with other fields
// adjusted accordingly (e.g., ingredient quantities scaled up)Tracking Users
Track which user made each decision for audit purposes.
user-tracking.ts
const decision = await pf.proposals.decide(proposalId, {
action: 'approve',
metadata: {
decidedBy: currentUser.id,
userEmail: currentUser.email,
ipAddress: request.ip,
},
});
console.log(decision.metadata.decidedBy); // 'user_123'
console.log(decision.createdAt); // '2024-01-15T10:30:00Z'Querying Decisions
query.ts
// List proposals by status
const { data: approved } = await pf.proposals.list({
status: 'approved',
limit: 50,
});
const { data: rejected } = await pf.proposals.list({
status: 'rejected',
});
// Each proposal includes its decision
for (const proposal of approved) {
console.log(proposal.decision?.finalObject);
}
// Filter by schema
const { data: blogPosts } = await pf.proposals.list({
status: 'approved',
schemaId: 'sch_abc123',
});Webhook Events
Set up webhooks to receive real-time notifications when decisions are made.
webhook.ts
// Register a webhook endpoint
await pf.webhooks.create({
url: 'https://your-app.com/webhooks/proposeflow',
events: ['proposal.approved', 'proposal.rejected'],
secret: 'whsec_...',
});
// Your webhook handler receives events like:
// {
// type: 'proposal.approved',
// data: {
// proposal: { id, schemaName, generatedObject, ... },
// decision: { id, action, finalObject, ... }
// }
// }