Integration Guide
Connect your internal systems to ProjectPilot using our public REST & GraphQL API and event webhooks. Generate an API key and register webhook endpoints in the Developer Platform, then follow this guide to integrate.
Overview
The ProjectPilot Developer Platform exposes your project, invoice, time, and agreement data over a single endpoint. Authenticate every request with an API key, then call REST routes or GraphQL. For real-time integration, subscribe to webhooks and verify each delivery with your endpoint secret.
Base API URL
https://globalzanrefsi.org/fn/dev/developerApiAuthentication
Create an API key in the Developer Platform. The full key is shown once — store it securely. Send it in thex-api-key header on every request. Keys are scoped; request only the scopes your integration needs.
GET /rest/projects HTTP/1.1
Host: globalzanrefsi.org
x-api-key: gz_live_8f3c9a2b7e1d4f6a
Accept: application/json| Scope | Grants |
|---|---|
| read:projects | List and read project details |
| read:invoices | List and read invoices |
| read:time | List and read time entries |
| read:agreements | Read signed agreements / milestones |
| write:invoices | Create or update invoices |
REST API
REST resources mirror your ProjectPilot data. All responses are JSON.
| Method | Path | Scope |
|---|---|---|
| GET | /rest/projects | read:projects |
| GET | /rest/projects/:id | read:projects |
| GET | /rest/invoices | read:invoices |
| POST | /rest/invoices | write:invoices |
| GET | /rest/time-entries | read:time |
| GET | /rest/agreements | read:agreements |
List projects
curl -H "x-api-key: gz_live_8f3c9a2b7e1d4f6a" \
https://globalzanrefsi.org/fn/dev/developerApi/rest/projectsExample response
{
"data": [
{
"id": "pp_3f8a...",
"name": "Acme Q3 Engagement",
"client_name": "Acme Corp",
"status": "active",
"hourly_rate": 180,
"currency": "USD"
}
]
}GraphQL
Prefer a single round-trip? POST a GraphQL query to /graphql with the samex-api-key header.
curl -X POST https://globalzanrefsi.org/fn/dev/developerApi/graphql \
-H "x-api-key: gz_live_8f3c9a2b7e1d4f6a" \
-H "Content-Type: application/json" \
-d '{"query": "{ projects { name } invoices { invoice_number amount } }"}'Rate limiting
Each key is capped at its configured requests per UTC minute (default 60/min). When exceeded the API returns 429 Too Many Requests. Inspect these headers to back off:
x-ratelimit-limit: 60
x-ratelimit-remaining: 0
x-ratelimit-reset: 1722230460Webhooks
Register an HTTPS endpoint in the Developer Platform and choose the events to subscribe to. We POST a JSON payload for each event; deliveries are retried on failure and signed so you can verify authenticity.
| Event | When it fires |
|---|---|
| invoice.created | Fired when a new invoice is generated from billable time/expenses |
| invoice.paid | Fired when an invoice status moves to paid |
| time_entry.created | Fired when a new time entry is logged on a project |
| agreement.signed | Fired when a client e-signs a proposal or milestone agreement |
Example payload
{
"event": "invoice.created",
"created_at": "2026-07-29T04:52:00Z",
"data": {
"id": "pp_inv_9c2a...",
"project_id": "pp_3f8a...",
"invoice_number": "INV-0042",
"amount": 5400.00,
"currency": "USD",
"status": "draft"
}
}Verifying signatures
Every delivery includes an x-pp-signature header computed asHMAC-SHA256 of the raw request body using your endpoint secret. Always verify before processing.
Node.js
import crypto from 'crypto';
function verify(signatureHeader, rawBody, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader)
);
}Python
import hmac, hashlib
def verify(signature_header, raw_body: bytes, secret: str) -> bool:
expected = hmac.new(
secret.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)Errors
Standard HTTP status codes are used. Errors return a JSON body with a message:
{
"error": "invalid_api_key",
"message": "The x-api-key header is missing or invalid."
}| 200 | Success |
| 400 | Malformed request |
| 401 | Missing or invalid API key |
| 403 | Key lacks the required scope |
| 429 | Rate limit exceeded |
| 5xx | Server error — retry with backoff |