This guide is for developers integrating with ZiplineOS — whether you're building a portal screen against the same API the product uses, or wiring an external system into a ZiplineOS organisation. It covers which auth model to pick, your first request, base URLs, error shapes, and rate limits.
ZiplineOS is a single ASP.NET 8 Minimal API service on Cloud Run, fronting a multi-tenant data model. Everything here is versioned under /api/v1/....
Pick your auth model first
The single most important decision is which authentication model your integration uses — it determines the base URL, the headers, and the rate-limit tier.
| Surface | Who it's for | Credential | Tenant scoping |
|---|---|---|---|
| Session cookie | First-party portals acting as a signed-in user | Firebase-issued __session cookie | X-Organization-Id header |
| Programmatic API token | Server-to-server integrations on a published Engine / webhook endpoint | X-Api-Key: zpl_… static token, or Authorization: Bearer | Resolved from the token's organisation |
| OAuth2 client credentials | Integrations that prefer a short-lived JWT over a static key | Authorization: Bearer <jwt> from POST /api/v1/oauth/token | Resolved from the credentials |
| Anonymous resource token | Public client-portal flows (Collect, magic links, Preference Centre) | A per-resource cryptographic token in the URL | Implicit — the token names one resource |
To choose the right model:
- Building a first-party portal screen? Use the session cookie model — it's what every authenticated screen already uses.
- Integrating an external service without a logged-in human? Use a programmatic API token or OAuth2 client credentials.
- Serving a public end-user flow via a one-off link? Use an anonymous resource token embedded in the URL.
Pick the row that matches your integration and skip the others.
Base URL
All endpoints live under /api/v1/.... The production base URL is:
https://api.ziplineos.com.au
A few path conventions worth knowing:
- Inbound webhooks land under
/api/v1/webhooks/...and use each provider's own auth check (for example, HMAC for Mailgun) rather than the cookie flow. - Published Engine endpoints live under
/api/v1/logic/webhook/{slug}.
Your first request
A. Session-cookie request (first-party / portal code)
The Firebase-backed session cookie is issued by POST /api/v1/auth/session; browser code typically already has it. Send the cookie and set the org id on every call:
const res = await fetch("https://api.ziplineos.com.au/api/v1/contacts", {
credentials: "include", // sends the __session cookie
headers: {
"X-Organization-Id": activeOrgId, // which tenant you're acting as
},
});
A request that authenticates but omits X-Organization-Id on a tenant-scoped endpoint is a hard 400. If your call drops credentials: "include", you'll get 401 on every request. These are the two most common first-day mistakes.
You can confirm which org ids you're allowed to use via GET /api/v1/users/me/organizations.
B. Programmatic API-token request (external integrations)
ZiplineOS issues scoped API tokens prefixed zpl_. These are managed on the API Tokens page in the Protocol portal and target published Engine endpoints. Both of the following headers are accepted:
# Using the X-Api-Key header
curl -X POST "https://api.ziplineos.com.au/api/v1/logic/webhook/<slug>" \
-H "Content-Type: application/json" \
-H "X-Api-Key: zpl_YOUR_TOKEN" \
-d '{"inputs": {"loanAmount": 50000}}'
# Or the standard Authorization: Bearer header — equivalent for api_key tokens
curl -X POST "https://api.ziplineos.com.au/api/v1/logic/webhook/<slug>" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer zpl_YOUR_TOKEN" \
-d '{"inputs": {"loanAmount": 50000}}'
A missing token returns 401 with "Missing API token. Provide via X-Api-Key or Authorization: Bearer header."
C. OAuth2 client-credentials (short-lived JWT)
Some Engine endpoints use an OAuth2 client-credentials flow. Exchange a Client ID + Client Secret for a JWT, then use it as a bearer token:
# 1. Exchange credentials for a JWT (valid ~24 hours)
curl -X POST "https://api.ziplineos.com.au/api/v1/oauth/token" \
-H "Content-Type: application/json" \
-d '{"grantType": "client_credentials", "clientId": "...", "clientSecret": "..."}'
# 2. Call the engine with the returned JWT
curl -X POST "https://api.ziplineos.com.au/api/v1/logic/webhook/<slug>" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_FROM_OAUTH_TOKEN" \
-d '{"inputs": {"loanAmount": 50000}}'
The JWT is checked for a per-engine scope of the form engine:<slug>:execute. Client credentials are managed on the API Tokens page in the Protocol portal alongside static tokens.
Working with API tokens
| Property | Behaviour |
|---|---|
| Prefix | Every token starts with zpl_ for easy identification in logs and config. |
| Shown once | The raw token is returned only at creation time; the server stores a SHA-256 hash. If you lose it, rotate — you can't recover. |
| Scoped | Each token is granted specific scopes (e.g. contacts:read, deals:write) rather than full access. |
| Expiry | Tokens carry an expiry between 1 and 365 days. |
| Revocable | Tokens can be revoked; a revoked token stops working within about a minute (~60s cache). |
Grant the narrowest scopes your integration needs, set a realistic expiry, and store the raw zpl_… value as a secret. Tokens are only ever shown once.
Response and error shapes
ZiplineOS uses conventional HTTP status codes. Common ones for integrators:
| Code | Meaning |
|---|---|
200 / 201 / 202 | Success — 201 for creates, 202 for async work enqueued |
400 | Validation failure or missing X-Organization-Id |
401 | Missing/invalid session cookie or token |
403 | Authenticated, but lacking the required role/membership |
404 | Not found, or not visible under your org scope |
429 | Rate limit exceeded |
500 | Unhandled exception — body includes a traceId |
Validation errors come back as 400, not 500. The body has a top-level error string (set to the first validation message) plus an errors map of field → string[]:
{
"error": "Name is required.",
"errors": {
"name": ["Name is required."],
"loanAmount": ["Must be greater than 0."]
}
}
Use the errors map for field-level handling — don't pattern-match on the top-level error string.
Rate limits
| Tier | Limit | Applies to |
|---|---|---|
| Global | 1200 req/min | All traffic (per IP) |
| ApiStandard | 300 req/min | Standard authenticated API calls (per user) |
| WebhookApi | 1000 req/min | Engine endpoints with X-Api-Key (per API key) |
| AuthStrict | 20 req/min | Login / session endpoints (per IP) |
On 429, back off and retry — the sliding window restores capacity quickly. A 429 can also come from the edge WAF before reaching the application; if your 429s don't correlate with the budgets above, the edge may be the source.
Quick checklist
- Confirm your auth model — cookie, programmatic token, OAuth2 JWT, or anonymous resource token.
- Use the production base URL
https://api.ziplineos.com.au, all paths under/api/v1/.... - Send the right headers every call — cookie code needs
credentials: "include"+X-Organization-Id; token code needsX-Api-KeyorAuthorization: Bearer. - Handle the standard error shapes — especially
400validation bodies (error+errorsmap) and401/403/429. - Respect rate limits — back off on
429; scope and expire tokens tightly.
