Back to Developer Platform

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/developerApi

Authentication

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.

http
GET /rest/projects HTTP/1.1
Host: globalzanrefsi.org
x-api-key: gz_live_8f3c9a2b7e1d4f6a
Accept: application/json
ScopeGrants
read:projectsList and read project details
read:invoicesList and read invoices
read:timeList and read time entries
read:agreementsRead signed agreements / milestones
write:invoicesCreate or update invoices

REST API

REST resources mirror your ProjectPilot data. All responses are JSON.

MethodPathScope
GET/rest/projectsread:projects
GET/rest/projects/:idread:projects
GET/rest/invoicesread:invoices
POST/rest/invoiceswrite:invoices
GET/rest/time-entriesread:time
GET/rest/agreementsread:agreements

List projects

bash
curl -H "x-api-key: gz_live_8f3c9a2b7e1d4f6a" \
  https://globalzanrefsi.org/fn/dev/developerApi/rest/projects

Example response

json
{
  "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.

bash
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:

http
x-ratelimit-limit: 60
x-ratelimit-remaining: 0
x-ratelimit-reset: 1722230460

Webhooks

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.

EventWhen it fires
invoice.createdFired when a new invoice is generated from billable time/expenses
invoice.paidFired when an invoice status moves to paid
time_entry.createdFired when a new time entry is logged on a project
agreement.signedFired when a client e-signs a proposal or milestone agreement

Example payload

json
{
  "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

javascript
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

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:

json
{
  "error": "invalid_api_key",
  "message": "The x-api-key header is missing or invalid."
}
200Success
400Malformed request
401Missing or invalid API key
403Key lacks the required scope
429Rate limit exceeded
5xxServer error — retry with backoff