Internal API v1
The Internal API gives Publica.la teams (Admin, Support, Product)
programmatic access to Farfalla data and actions, separate from the
customer-facing v1, v2, and v3 APIs. It runs as a Bearer-token API
mounted at internal-api/v1/* on the platform host only.
At a glance:
- Bearer token authentication, scoped to API clients (not to users)
- Per-token abilities (
tenants:read,tenants:write,cross-tenant) gate every route - 60 requests per minute per token
- Tokens do not expire. Rotation happens through revoke and re-issue
Base URL
https://app.publica.la/internal-api/v1/
The API only answers on the platform host (app.publica.la).
Requests that resolve to any other host return 404. There is no
per-tenant subdomain entry point.
Authentication
Every request must carry a Bearer token in the Authorization
header.
Authorization: Bearer <id>|pla_int_<secret>
Tokens carry a pla_int_ prefix to make them recognizable to
secret-scanning tools. The full token is shown once at issuance
and never again. Only a hash and the last four characters are stored
on the server.
The Internal API does not read the X-User-Token header. A request
that authenticates with X-User-Token instead of a Bearer returns 401.
Issuance
Token issuance is restricted to engineering. The flow is:
- Engineering opens the Api Clients panel in Nova.
- Picks an existing client or creates a new one and runs the Issue token action.
- Selects the abilities to grant.
- The plaintext token appears once on a one-shot reveal page.
There is no self-service flow and no public issuance endpoint. Coordinate with engineering to receive a token.
Revocation
Engineering revokes a token through Nova. The token stops working on the next request, with no graceful-period or warning state.
Abilities
Each token carries a set of abilities. Routes declare which abilities they require. A token without the required ability gets a 403:
{ "message": "Invalid ability provided." }
| Ability | Required by |
|---|---|
tenants:read | Reading the tenant directory |
tenants:write | Mutating tenant state (mark/clear missing payments) |
cross-tenant | Endpoints whose URL has no {tenant} segment |
Naming convention: <resource>:<action>. New abilities ship
alongside the endpoints that introduce them.
Tenant scoping
URLs come in two shapes.
Tenant-scoped: the URL names the customer tenant
/internal-api/v1/tenants/{tenant}/missing-payments
The {tenant} segment names the customer tenant the call acts on.
The token's tenants:write ability authorizes the action.
Operations against the platform tenant (id 1) are rejected with
403 and the message
Operations on the root tenant are not allowed via internal-api.
Internal API endpoints target customer tenants, not the platform
tenant itself.
Cross-tenant: the URL has no tenant segment
/internal-api/v1/tenants
These routes return data spanning every tenant on the platform. They
require the cross-tenant ability in addition to the
resource-level read or write ability.
The X-Farfalla-Tenant-Id header is not used by the Internal API.
Headers reference
| Header | Description | Required |
|---|---|---|
Authorization | Bearer <id>|pla_int_<secret> | All endpoints |
Accept | application/json | Recommended |
Content-Type | application/json | POST, PUT, PATCH |
The API always responds with JSON, including for error responses (auth failures, throttling, host rejections).
Pagination
List endpoints use cursor pagination.
{
"data": [...],
"links": {
"next": "https://app.publica.la/internal-api/v1/tenants?cursor=eyJpZCI6MTQs...",
"prev": null
},
"meta": {
"has_more": true
}
}
Pass the links.next URL back as-is for the following page. Iterate
until meta.has_more is false. Treat the cursor value as opaque,
its format is an implementation detail and may change without
notice.
Response conventions
- Ids are strings. Every resource id in a response is serialized
as a string. Keeps JavaScript clients precision-safe, since
Numberloses precision above 2^53.
Rate limiting
Each token has a 60 RPM budget per calendar minute. A token over budget gets 429 until the window resets.
HTTP/1.1 429 Too Many Requests
Retry-After: <seconds until window resets>
{
"message": "Too Many Attempts."
}
Audit log
Every authenticated call is recorded for review. Engineering can surface call history per API client when investigating issues.
Error handling
| Code | Meaning |
|---|---|
| 200 | Success |
| 400 | Domain refusal (the action cannot proceed against current state) |
| 401 | Missing or invalid Bearer |
| 403 | Token lacks the required ability, or the URL targets the platform tenant |
| 404 | Wrong host, or {tenant} not found |
| 422 | Request body validation failure |
| 429 | Rate-limit budget exhausted |
| 500 | Unhandled server error |
Errors come in two envelope shapes:
- Domain refusals (400) carry a stable machine-readable code:
{ "error": { "code": "...", "message": "..." } }. Match onerror.code. - Framework rejections (auth, ability, host, throttle, route
binding) carry a single
messagestring:{ "message": "..." }. Match on the status code, not on the message text.
Domain refusal (400)
Endpoint-specific validation that the request cannot proceed. The envelope includes a stable machine-readable code:
{
"error": {
"code": "tenant_has_no_stripe_customer",
"message": "Tenant has no stripe customer."
}
}
Auth failure (401)
{ "message": "Unauthenticated." }
Ability failure (403)
{ "message": "Invalid ability provided." }
Root-tenant guard (403)
{ "message": "Operations on the root tenant are not allowed via internal-api." }
Not found (404)
{ "message": "Not found" }
Rate limit (429)
HTTP/1.1 429 Too Many Requests
Retry-After: <seconds until window resets>
{
"message": "Too Many Attempts."
}
Endpoint summary
Tenants
| Method | Endpoint | Required abilities | Description |
|---|---|---|---|
GET | /tenants | tenants:read, cross-tenant | List tenants with an active plan |
POST | /tenants/{tenant}/missing-payments | tenants:write | Flag a tenant as having unpaid invoices |
DELETE | /tenants/{tenant}/missing-payments | tenants:write | Clear the unpaid-invoices flag |
Next steps
- Tenants Overview - resource overview and route shapes
- List Tenants -
GET /tenants - Mark Missing Payments -
POST /tenants/{tenant}/missing-payments - Clear Missing Payments -
DELETE /tenants/{tenant}/missing-payments
See also
- Farfalla API v2 Overview - mobile app backend (JWT, customer-facing)
- Public Rest API v3 - customer-facing programmatic API