Proposals API
Create, list, update, and archive proposals via the REST API.
3 min read · Last updated:
List Proposals
GET /api/v1/proposals
Scope: proposals:read
Query parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| page | integer | 1 | Page number |
| limit | integer | 20 | Items per page (max 50) |
| status | string | — | Filter: draft, analyzing, analyzed, generating, ready, sent, archived |
| clientId | uuid | — | Filter by client |
| sort | string | created_at | Sort field |
| order | string | desc | asc or desc |
Response
{
"data": [
{
"id": "360db97a-516e-43f7-a14f-91e4cf829551",
"title": "Website Redesign Proposal",
"status": "ready",
"clientId": "a1b2c3d4-...",
"pricingModel": "fixed",
"finalPrice": 15000,
"rawInputMethod": "paste",
"createdAt": "2026-03-15T08:30:00.000Z",
"updatedAt": "2026-03-15T09:15:00.000Z"
}
],
"meta": {
"requestId": "...",
"timestamp": "...",
"page": 1,
"limit": 20,
"total": 47,
"hasMore": true
}
}
Create Proposal
POST /api/v1/proposals
Scope: proposals:write
Request body
{
"title": "Website Redesign Proposal",
"rawInputText": "We need a complete redesign of our e-commerce site...",
"rawInputMethod": "paste",
"clientId": "a1b2c3d4-...",
"templateId": "t1e2m3p4-..."
}
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| title | string | No | Defaults to "Untitled Proposal" |
| rawInputText | string | No | The client brief text |
| rawInputMethod | string | No | paste, upload, or url (default: paste) |
| clientId | uuid | No | Link to an existing client |
| templateId | uuid | No | Base proposal on a template |
Response — 201 Created
{
"data": {
"id": "ff470a6d-5694-4755-85ae-982e58020f2e",
"title": "Website Redesign Proposal",
"status": "draft",
"clientId": "a1b2c3d4-...",
"pricingModel": null,
"finalPrice": null,
"rawInputMethod": "paste",
"createdAt": "2026-03-17T10:30:00.000Z",
"updatedAt": "2026-03-17T10:30:00.000Z"
},
"meta": { "requestId": "...", "timestamp": "..." }
}
Get Proposal
GET /api/v1/proposals/:id
Scope: proposals:read
Response
{
"data": {
"id": "360db97a-...",
"title": "Website Redesign Proposal",
"status": "ready",
"clientId": "a1b2c3d4-...",
"rawInputMethod": "paste",
"rawInputUrl": null,
"generatedProposalMarkdown": "# Website Redesign Proposal\n\n...",
"editedProposalMarkdown": "# Website Redesign Proposal\n\n...",
"pricingModel": "fixed",
"totalPriceLow": 12000,
"totalPriceMid": 15000,
"totalPriceHigh": 20000,
"finalPrice": 15000,
"pricingBreakdown": [
{ "item": "Design", "hours": 40, "rate": 150, "total": 6000 }
],
"estimatedDurationDays": 45,
"timelineMilestones": [
{ "name": "Discovery", "durationDays": 5, "description": "..." }
],
"proposedStartDate": null,
"proposalScore": 87,
"scoreBreakdown": {
"overall": 87,
"clarity": 90,
"completeness": 85,
"persuasiveness": 86
},
"analysisSummary": { "hasResult": true },
"scopeSummary": { "hasResult": true },
"outcome": null,
"outcomeNotes": null,
"createdAt": "2026-03-15T08:30:00.000Z",
"updatedAt": "2026-03-15T09:15:00.000Z"
},
"meta": { "requestId": "...", "timestamp": "..." }
}
Update Proposal
PATCH /api/v1/proposals/:id
Scope: proposals:write
Only proposals in draft or ready status can be updated.
Request body (all fields optional)
{
"title": "Updated Title",
"rawInputText": "Updated brief text...",
"clientId": "a1b2c3d4-...",
"pricingModel": "fixed",
"finalPrice": 18000,
"outcome": "won",
"outcomeNotes": "Client accepted after minor revisions"
}
| Field | Type | Description |
|-------|------|-------------|
| title | string | Proposal title |
| rawInputText | string | Client brief text |
| clientId | uuid | Link to client |
| pricingModel | string | hourly, fixed, milestone, retainer |
| finalPrice | integer | Final agreed price (in cents) |
| outcome | string | won, lost, no_response, cancelled |
| outcomeNotes | string | Notes about the outcome |
Response — 200 OK
Returns the full proposal detail object (same shape as Get Proposal).
Archive Proposal
DELETE /api/v1/proposals/:id
Scope: proposals:write
Soft-deletes the proposal by setting its status to archived.
Response
{
"data": {
"id": "360db97a-...",
"status": "archived"
},
"meta": { "requestId": "...", "timestamp": "..." }
}