API & webhook reference
Pull your organization's proposals, milestones, and files programmatically, and get notified in real time via webhooks.
Getting an API key
Generate a key from the Developer section of Client Portal → Security. The raw key is shown once at creation — store it securely. Every request must include it as a Bearer token:
Authorization: Bearer aftpak_live_<your key>Scopes
Each key is created as Read only or Read & write. Every GET endpoint below works with either scope; every POST endpoint requires a Read & write key and returns 403 otherwise.
Endpoints
GET /api/v1/proposals
curl -H "Authorization: Bearer aftpak_live_<key>" \
https://aftpak.com/api/v1/proposalsResponse
{
"proposals": [
{
"id": "cljk...",
"organizationId": "cljk...",
"proposalCode": "PRP-104",
"title": "...",
"module": "...",
"status": "Approved",
"version": "v1.0",
"submittedDate": "2026-06-01",
"summary": "...",
"scopeHighlights": ["..."],
"approvedDate": "2026-06-10",
"revisionNote": null,
"updatedAt": "2026-06-10T12:00:00.000Z"
}
]
}POST /api/v1/proposals
Requires a Read & write key.
curl -X POST -H "Authorization: Bearer aftpak_live_<key>" \
-H "Content-Type: application/json" \
-d '{
"proposalCode": "PRP-105",
"title": "...",
"module": "...",
"summary": "...",
"scopeHighlights": ["...", "..."]
}' \
https://aftpak.com/api/v1/proposalsResponse — 201 Created
{ "proposal": { "id": "cljk...", "proposalCode": "PRP-105", ... } }GET /api/v1/milestones
curl -H "Authorization: Bearer aftpak_live_<key>" \
https://aftpak.com/api/v1/milestonesResponse
{
"milestones": [
{
"id": "cljk...",
"organizationId": "cljk...",
"phase": "Requirement Analysis",
"status": "Completed",
"description": "...",
"date": "2026-06-01",
"sortOrder": 1
}
]
}POST /api/v1/milestones
Requires a Read & write key.
curl -X POST -H "Authorization: Bearer aftpak_live_<key>" \
-H "Content-Type: application/json" \
-d '{
"phase": "Deployment",
"description": "...",
"date": "2026-08-01",
"sortOrder": 5
}' \
https://aftpak.com/api/v1/milestonesResponse — 201 Created
{ "milestone": { "id": "cljk...", "phase": "Deployment", "status": "Pending", ... } }GET /api/v1/files
curl -H "Authorization: Bearer aftpak_live_<key>" \
https://aftpak.com/api/v1/filesResponse
{
"files": [
{
"id": "cljk...",
"name": "Requirement_Analysis_Summary.pdf",
"type": "PDF",
"size": "220 KB",
"uploadedDate": "2026-06-01",
"phase": "Requirement Analysis"
}
]
}File metadata only — downloading file bytes requires an authenticated Client Portal session, not an API key.
Errors
A missing, invalid, or revoked API key returns:
HTTP/1.1 401 Unauthorized
{ "error": "Not authorized" }A POST endpoint called with a Read only key returns:
HTTP/1.1 403 Forbidden
{ "error": "This API key does not have the required scope" }Each API key is limited to 100 requests per minute. Exceeding it returns:
HTTP/1.1 429 Too Many Requests
{ "error": "Rate limit exceeded" }Webhooks
Configure a webhook URL from the same Developer section to receive a signed POST request whenever one of the events below happens for your organization.
Event types
activity.created— new activity logged for your organizationinvoice.paid— an invoice is marked as paidmilestone.completed— a project milestone is marked completeticket.created— a new support ticket is openedticket.replied— a support ticket gets a new reply, from either sidesurvey.responded— a client responds to a milestone satisfaction survey
Request body (activity.created example)
{
"event": "activity.created",
"payload": {
"id": "cljk...",
"organizationId": "cljk...",
"date": "2026-06-01",
"type": "proposal",
"message": "...",
"createdAt": "2026-06-01T12:00:00.000Z"
},
"timestamp": 1749000000000
}Signature header — verify with the webhook secret shown at creation
X-Webhook-Signature: <hex HMAC-SHA256 of the raw request body>Verifying in Node
const crypto = require("crypto");
function isValid(rawBody, signature, secret) {
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return expected === signature;
}See what's shipped recently in our Changelog.