API Reference
The Manager exposes two API surfaces with distinct jobs:
- GraphQL (
POST /query) is the management API — projects, tenants, applications, environments, users, roles, settings, deployment specs, and triggers. It is the same API the Manager UI and thepfyCLI use; authenticate with a PAT. - REST covers machine-to-machine and delivery concerns — backend registration (
/api/machine/*), proxy validation (/api/validate-*), frontend configuration, language packs, the pilet feed, and the ESB. (CLI login/api/cli/*is planned, not implemented.)
Base URLs
- GraphQL Endpoint:
POST /query - GraphQL Playground (dev only):
GET /gql - REST API:
/api/*,/pilets/*,/pilet-feed/*,/language-packs/*, and/esb/*
Authentication
The Manager supports three authentication methods:
1. Bearer Token (Personal Access Tokens)
Personal Access Tokens (PATs) provide secure API access for users. PATs authenticate requests to the GraphQL endpoint (/query) and the pilet REST endpoints (/pilets/*).
Header Format:
Authorization: Bearer <your-token>Creating a PAT:
mutation {
createPersonalAccessToken(
name: "My API Token"
expiresAt: "2026-12-31T23:59:59Z"
) {
token
personalAccessToken {
id
name
tokenPrefix
expiresAt
createdAt
}
}
}WARNING
The token is only shown once during creation and cannot be retrieved later. Store it securely.
2. Machine User Authentication
Machine users enable backend services to authenticate with the Manager. Machine user credentials authenticate the /api/machine/* and /esb/* endpoints (not the GraphQL API).
Bearer Token (Recommended):
Authorization: Bearer <machine-user-token>Basic Authentication:
Authorization: Basic <base64(username:password)>WARNING
The Manager's own machine endpoints (/api/machine/*, /esb/*) accept Bearer tokens only. Basic authentication for machine users is supported when authenticating through the Proxy.
Creating a Machine User:
mutation {
createMachineUserWithCredentials(
tenantId: "tenant-uuid"
input: {
name: "Backend Service"
username: "backend-service"
hashedKey: "__GENERATE_TOKEN__"
enabled: true
}
) {
generatedToken
machineUser {
id
username
}
}
}3. User Session (OAuth/OIDC)
Standard web session authentication. OAuth/OIDC login is handled by the Proxy, which validates the session with the Manager and forwards the user's identity on each request.
GraphQL API
Schema Overview
The GraphQL API provides type-safe access to all Manager resources with filtering, ordering, and pagination support.
GraphQL Playground: Access at /gql in development mode to explore the schema interactively.
Common Types
Filter
input Filter {
field: String!
value: String!
}Order
input Order {
field: String!
direction: OrderDirection!
}
enum OrderDirection {
ASC
DESC
}Pagination
input Pagination {
limit: Int!
offset: Int!
}Allowed Limits
limit must be one of 1, 10, 20, 25, 50, or 100. Other values are rejected.
Endpoint Types
Authentication Types:
enum EndpointAuthType {
Basic # HTTP Basic Authentication
Bearer # Bearer Token Authentication
None # No authentication required
}Message Types:
enum EndpointInputMessageType {
SOAP # SOAP XML message
XML # Plain XML
JSON # JSON payload
}
enum EndpointOutputMessageType {
SOAP # SOAP XML message
XML # Plain XML
JSON # JSON payload
}Log Levels:
enum EndpointLogLevel {
NONE # No logging
BASIC # Log metadata only (headers, status, timing)
FULL # Log full request/response bodies
}Queries
Projects
List Projects
query {
projects(
filters: []
order: { field: "createdAt", direction: DESC }
pagination: { limit: 10, offset: 0 }
) {
id
name
slug
createdAt
}
}Get Project by Slug
query {
project(slug: "my-project") {
id
name
slug
tenants {
id
name
}
}
}Tenants
List Tenants
query {
tenants(
projectSlug: "my-project"
filters: []
order: { field: "name", direction: ASC }
pagination: { limit: 20, offset: 0 }
) {
id
name
slug
type
}
}Get Tenant
query {
tenant(projectId: "project-uuid", slug: "tenant-slug") {
id
name
slug
applications {
id
name
}
}
}Applications
List Applications
query {
applications(
tenantSlug: "tenant-slug"
filters: []
order: { field: "name", direction: ASC }
pagination: { limit: 10, offset: 0 }
) {
id
name
slug
description
endpoints {
id
name
url
}
}
}Get Application
query {
application(
projectId: "project-uuid"
tenantId: "tenant-uuid"
slug: "app-slug"
) {
id
name
slug
description
triggers {
id
name
cronExpression
enabled
}
}
}Settings
Get Settings
query {
settings(
project: "project-uuid"
tenant: "tenant-uuid"
application: "app-uuid"
) {
id
backendSetting {
id
moduleConfig {
EnabledModules
KeyValuePairs {
Key
Value
}
}
}
frontendSetting {
id
moduleConfig {
EnabledThemes
DefaultTheme
EnabledLocales
DefaultLocale
}
}
}
}Configuration Inheritance
Settings follow a three-layer hierarchy: Project → Tenant → Application. This query returns the settings attached at the requested level. The effective merged configuration (project → tenant → application) is served by the REST endpoint GET /api/frontend-config/:application_id.
Endpoints
List Endpoints
query {
endpoints(
application: "app-uuid"
filters: []
order: { field: "name", direction: ASC }
pagination: { limit: 50, offset: 0 }
) {
id
name
description
enabled
url
consumeURL
authType
}
}Triggers
List Triggers
query {
triggers(
application: "app-uuid"
filters: []
order: { field: "name", direction: ASC }
pagination: { limit: 50, offset: 0 }
) {
id
name
cronExpression
enabled
createdAt
updatedAt
}
}Machine Users
List Machine Users
query {
machineUsers(
tenant: "tenant-uuid"
filters: []
order: { field: "name", direction: ASC }
pagination: { limit: 20, offset: 0 }
) {
id
name
username
enabled
tokenPrefix
}
}Language Packs
List Language Packs
query {
languagePacks(
project: "project-uuid"
filters: []
order: { field: "name", direction: ASC }
pagination: { limit: 20, offset: 0 }
) {
id
name
content {
Key
Value
}
}
}Personal Access Tokens
List My Personal Access Tokens
query {
myPersonalAccessTokens(
filters: []
order: { field: "createdAt", direction: DESC }
pagination: { limit: 20, offset: 0 }
) {
id
name
tokenPrefix
expiresAt
createdAt
lastUsedAt
}
}List Personal Access Tokens (Cursor-based)
query {
personalAccessTokens(first: 20, after: "cursor-string") {
edges {
node {
id
name
tokenPrefix
expiresAt
createdAt
lastUsedAt
}
cursor
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
}
}Token Security
Only the token prefix is visible after creation. The full token is only shown once during creation.
Users
Get Current User
query {
me {
id
externalName
externalUsername
externalEmail
role
}
}List Users
query {
users(
filters: []
order: { field: "externalName", direction: ASC }
pagination: { limit: 20, offset: 0 }
) {
id
externalName
externalUsername
externalEmail
role
}
}Search Provider Users
query {
userProviderSearch(name: "john") {
id
username
email
displayName
disabled
}
}User Relationships
List User-Application Relations
query {
user2applications(
application: "app-uuid"
filters: []
order: { field: "createdAt", direction: DESC }
pagination: { limit: 20, offset: 0 }
) {
id
role
user {
id
externalUsername
externalEmail
}
application {
id
name
}
}
}Get User-Application Relation
query {
user2application(application: "app-uuid", id: "relation-uuid") {
id
role
user {
externalUsername
}
}
}List User-Project Relations
query {
user2projects(
project: "project-uuid"
filters: []
order: { field: "createdAt", direction: DESC }
pagination: { limit: 20, offset: 0 }
) {
id
role
user {
id
externalUsername
externalEmail
}
project {
id
name
}
}
}Get User-Project Relation
query {
user2project(project: "project-uuid", id: "relation-uuid") {
id
role
user {
externalUsername
}
}
}List User-Tenant Relations
query {
user2tenants(
tenant: "tenant-uuid"
filters: []
order: { field: "createdAt", direction: DESC }
pagination: { limit: 20, offset: 0 }
) {
id
role
user {
id
externalUsername
externalEmail
}
tenant {
id
name
}
}
}Get User-Tenant Relation
query {
user2tenant(tenant: "tenant-uuid", id: "relation-uuid") {
id
role
user {
externalUsername
}
}
}Machine User to Endpoint Relations
List Machine User-Endpoint Relations
query {
machineUser2endpoints(
endpoint: "endpoint-uuid"
filters: []
order: { field: "createdAt", direction: DESC }
pagination: { limit: 20, offset: 0 }
) {
id
enabled
machineUser {
id
username
}
endpoint {
id
name
}
}
}Get Machine User-Endpoint Relation
query {
machineUser2endpoint(endpoint: "endpoint-uuid", id: "relation-uuid") {
id
enabled
}
}Audit Logs
List Audit Logs
query {
auditLogs(
projectID: "project-uuid"
filters: [{ field: "action", value: "create" }]
order: { field: "createdAt", direction: DESC }
pagination: { limit: 50, offset: 0 }
) {
id
entityType
entityID
action
description
metadata
changes
ipAddress
userAgent
createdAt
user {
externalUsername
}
}
}Pilet Packages
List Pilet Packages
query {
piletPackages(projectId: "project-uuid", enabled: true) {
packages {
id
name
version
description
size
hash
enabled
createdAt
updatedAt
}
count
}
}Get Pilet Package
query {
piletPackage(
projectId: "project-uuid"
name: "@my-org/my-pilet"
version: "1.0.0"
) {
id
name
version
description
size
hash
enabled
createdAt
updatedAt
}
}Mutations
Project Management
Create Project
mutation {
createProject(input: { name: "My Project", slug: "my-project" }) {
id
name
slug
}
}Update Project
mutation {
updateProject(
id: "project-uuid"
input: { name: "Updated Project Name", slug: "updated-project" }
) {
id
name
slug
}
}Delete Project
mutation {
deleteProject(id: "project-uuid")
}Tenant Management
Create Tenant
mutation {
createTenant(
projectId: "project-uuid"
input: { name: "Customer A", slug: "customer-a", type: run }
) {
id
name
slug
type
}
}Update Tenant
mutation {
updateTenant(id: "tenant-uuid", input: { name: "Updated Customer Name" }) {
id
name
}
}Delete Tenant
mutation {
deleteTenant(id: "tenant-uuid")
}Application Management
Create Application
mutation {
createApplication(
tenantId: "tenant-uuid"
input: {
name: "Web App"
slug: "web-app"
description: "Main web application"
}
) {
id
name
slug
}
}Update Application
mutation {
updateApplication(
id: "app-uuid"
input: { description: "Updated description" }
) {
id
description
}
}Delete Application
mutation {
deleteApplication(id: "app-uuid")
}Promote Application
Promote/copy an application version to another tenant (environment):
mutation {
promoteApplication(
input: {
applicationId: "app-uuid"
targetTenantId: "prod-tenant-uuid"
slug: "app-slug"
name: "Application Name"
version: "1.0.0"
}
) {
id
name
slug
version
}
}Copy Application
Copy an application to another tenant with a new slug:
mutation {
copyApplication(
input: {
sourceApplicationId: "app-uuid"
targetTenantId: "new-tenant-uuid"
slug: "new-app-slug"
name: "New Application Name"
version: "1.0.0"
}
) {
id
name
slug
version
}
}Settings Management
Configuration Inheritance
Settings can be defined at three levels: Project → Tenant → Application. A Settings container links a BackendSettings and a FrontendSettings row to one of these levels (via createSettings with projectID, tenantID, or applicationID). The actual configuration values are managed with the updateBackendModuleConfig and updateFrontendModuleConfig mutations below.
Delete Backend Settings
mutation {
deleteBackendSettings(id: "backend-settings-uuid")
}Delete Frontend Settings
mutation {
deleteFrontendSettings(id: "frontend-settings-uuid")
}Update Backend Module Configuration
mutation {
updateBackendModuleConfig(
id: "backend-settings-uuid"
input: {
EnabledModules: ["module1", "module2"]
KeyValuePairs: [
{ Key: "feature_flag_1", Value: "true" }
{ Key: "api_timeout", Value: "30" }
]
}
) {
id
moduleConfig {
EnabledModules
KeyValuePairs {
Key
Value
}
}
}
}Update Frontend Module Configuration
mutation {
updateFrontendModuleConfig(
id: "frontend-settings-uuid"
input: {
EnabledThemes: ["dark", "light"]
DefaultTheme: "light"
EnabledLocales: ["en", "hu"]
DefaultLocale: "en"
KeyValuePairs: [{ Key: "show_footer", Value: "true" }]
}
) {
id
moduleConfig {
DefaultTheme
DefaultLocale
}
}
}Language Pack Management
Create Language Pack
mutation {
createLanguagePack(projectId: "project-uuid", input: { name: "en-US" }) {
id
name
}
}Update Language Pack Content
mutation {
updateLanguagePackContent(
id: "language-pack-uuid"
input: {
content: [
{ Key: "welcome", Value: "Welcome" }
{ Key: "login", Value: "Login" }
{ Key: "logout", Value: "Logout" }
]
}
) {
id
content {
Key
Value
}
}
}Endpoint Management
Create Endpoint
mutation {
createEndpoint(
applicationId: "app-uuid"
input: {
name: "Webhook Handler"
description: "Processes incoming webhooks"
enabled: true
consumeURL: "/api/webhook"
url: "https://backend.example.com/webhook"
authType: Bearer
inputMessageType: JSON
outputMessageType: JSON
logLevel: BASIC
}
) {
id
name
consumeURL
url
authType
inputMessageType
outputMessageType
logLevel
}
}Field Descriptions:
consumeURL- The URL the ESB consumes/calls (required); incoming/esbrequests are matched against this valueurl- Optional internal URL used for routingauthType- Authentication method:Basic,Bearer, orNone(default:None)inputMessageType- Expected input format:JSON,XML, orSOAP(optional)outputMessageType- Output format after transformation:JSON,XML, orSOAP(optional)logLevel- ESB logging verbosity:NONE,BASIC, orFULL(default:BASIC)
Update Endpoint
mutation {
updateEndpoint(
id: "endpoint-uuid"
input: {
enabled: false
url: "https://new-backend.example.com/webhook"
logLevel: FULL
}
) {
id
enabled
url
logLevel
}
}Delete Endpoint
mutation {
deleteEndpoint(id: "endpoint-uuid")
}Trigger Management
Create Trigger
mutation {
createTrigger(
applicationId: "app-uuid"
input: {
name: "Daily Report"
description: "Generates daily report"
cronExpression: "0 0 * * *"
runKey: "daily-report"
enabled: true
}
) {
id
name
cronExpression
runKey
}
}Update Trigger
mutation {
updateTrigger(
id: "trigger-uuid"
input: { enabled: false, cronExpression: "0 2 * * *" }
) {
id
enabled
cronExpression
}
}Machine User Management
Create Machine User with Token
mutation {
createMachineUserWithCredentials(
tenantId: "tenant-uuid"
input: {
name: "API Service"
username: "api-service"
hashedKey: "__GENERATE_TOKEN__"
enabled: true
}
) {
generatedToken
machineUser {
id
username
tokenPrefix
}
}
}Token Generation
Use hashedKey: "__GENERATE_TOKEN__" to auto-generate a Bearer token. The plaintext token is only returned once.
Update Machine User
mutation {
updateMachineUser(id: "machine-user-uuid", input: { enabled: false }) {
id
enabled
}
}Delete Machine User
mutation {
deleteMachineUser(id: "machine-user-uuid")
}Machine User to Endpoint Access
Create Machine User-Endpoint Access
mutation {
createMachineUser2Endpoint(
input: {
machineUserID: "machine-user-uuid"
endpointID: "endpoint-uuid"
enabled: true
}
) {
id
enabled
machineUser {
username
}
endpoint {
name
}
}
}Update Machine User-Endpoint Access
mutation {
updateMachineUser2Endpoint(id: "relation-uuid", input: { enabled: false }) {
id
enabled
}
}Delete Machine User-Endpoint Access
mutation {
deleteMachineUser2Endpoint(id: "relation-uuid")
}Personal Access Token Management
Create Personal Access Token
mutation {
createPersonalAccessToken(
name: "Production API Token"
expiresAt: "2026-12-31T23:59:59Z"
) {
token
personalAccessToken {
id
name
tokenPrefix
expiresAt
createdAt
}
}
}One-Time Token Display
The token field contains the full plaintext token and is only returned once. Store it securely.
Revoke Personal Access Token
mutation {
revokePersonalAccessToken(id: "pat-uuid") {
id
name
revoked
}
}Delete Personal Access Token
mutation {
deletePersonalAccessToken(id: "pat-uuid")
}User Access Management
Simplified User Management
The following mutations are the supported interface for user access management. They validate role assignments against the caller's own role.
Add User to Project
mutation {
addUserToProject(
projectID: "project-uuid"
userID: "user-uuid"
role: developer
) {
id
role
user {
externalUsername
}
}
}Available Roles: readonly, tester, developer, maintainer, owner
Update User Project Role
mutation {
updateUserProjectRole(id: "relation-uuid", role: readonly) {
id
role
}
}Remove User from Project
mutation {
removeUserFromProject(id: "relation-uuid")
}Add User to Tenant
mutation {
addUserToTenant(tenantID: "tenant-uuid", userID: "user-uuid", role: normal) {
id
role
}
}Available Roles: readonly, normal, tester, developer, maintainer, owner
Update User Tenant Role
mutation {
updateUserTenantRole(id: "relation-uuid", role: developer) {
id
role
}
}Remove User from Tenant
mutation {
removeUserFromTenant(id: "relation-uuid")
}Add User to Application
mutation {
addUserToApplication(
applicationID: "app-uuid"
userID: "user-uuid"
role: normal
) {
id
role
}
}Available Roles: readonly, normal
Update User Application Role
mutation {
updateUserApplicationRole(id: "relation-uuid", role: readonly) {
id
role
}
}Remove User from Application
mutation {
removeUserFromApplication(id: "relation-uuid")
}Pilet Package Management
Update Pilet Package
mutation {
updatePiletPackage(
projectId: "project-uuid"
name: "@my-org/my-pilet"
version: "1.0.0"
description: "Updated description"
enabled: true
) {
id
name
version
description
enabled
}
}Pilet Upload
The actual pilet file upload happens via the REST API endpoint POST /pilets/:projectId/:name/:version. This mutation is for metadata updates only.
Enable Pilet Package
mutation {
enablePiletPackage(
projectId: "project-uuid"
name: "@my-org/my-pilet"
version: "1.0.0"
) {
id
enabled
}
}Disable Pilet Package
mutation {
disablePiletPackage(
projectId: "project-uuid"
name: "@my-org/my-pilet"
version: "1.0.0"
) {
id
enabled
}
}REST API
Check Machine User Auth
Endpoint: GET /api/machine/check
Description: Verifies machine user authentication status.
Authentication: Bearer Token (Machine User)
Response:
{
"authenticated": true,
"machine_user_id": "machine-user-uuid",
"tenant_id": "tenant-uuid"
}Register Backend
Endpoint: POST /api/machine/register-backend
Description: Registers a backend instance for trigger execution callbacks. Re-posting for the same application updates the existing registration (and refreshes its heartbeat) while keeping the same callback token.
Authentication: Bearer Token (Machine User)
Request Body:
{
"application_id": "app-uuid",
"callback_url": "https://payment.example.com/triggers/callback",
"version": "1.2.3"
}Response:
{
"success": true,
"registered_id": "backend-uuid",
"token": "callback-bearer-token",
"message": "Backend registered successfully",
"tenant_id": "tenant-uuid",
"application_id": "app-uuid"
}The returned token is the Bearer token the Manager uses when calling the backend's trigger callback URL.
Backend Heartbeat
Endpoint: PUT /api/machine/backend/:id/heartbeat
Description: Lightweight liveness signal for a registered backend (default cadence ~30 s). Only updates the registration's last_heartbeat — no re-verification. A 404 means the registration is gone (Manager restarted or reaped); clients re-register and resume with the new id.
Authentication: Bearer Token (Machine User)
Response: 204 No Content on success, 404 if the registration does not exist or is not yours.
Unregister Backend
Endpoint: DELETE /api/machine/backend/:id
Description: Graceful deregistration on shutdown. Idempotent — a 404 is tolerated by clients.
Authentication: Bearer Token (Machine User)
Response: 204 No Content
Application Tenants
Endpoint: GET /api/machine/application/:id/tenants
Description: Lists the tenants that have the application enabled. Backends use it to fan scheduled work out per tenant from a single trigger delivery, and to pre-warm per-tenant config caches.
Authentication: Bearer Token (Machine User)
Response:
{
"tenants": [
{ "id": "tenant-uuid", "name": "acme", "enabled": true },
{ "id": "tenant-uuid-2", "name": "beta", "enabled": true }
]
}Validate Proxy Auth
Endpoint: POST /api/validate-proxy-auth
Description: Validates an Authorization header (PAT, or machine user Bearer/Basic credentials) on behalf of the Proxy. Internal endpoint — calls must carry the proxy shared secret.
Request Body:
{
"authorization_header": "Bearer <token>",
"application_id": "app-uuid"
}Response:
{
"valid": true,
"user": {
"id": "user-uuid",
"email": "user@example.com",
"name": "User Name",
"username": "username",
"is_machine_user": false,
"is_personal_token": true
},
"tenant": null,
"application": {
"has_access": true,
"role": "normal",
"available_tenants": [{ "id": "tenant-uuid", "name": "Tenant Name" }]
}
}Validate OAuth User
Endpoint: POST /api/validate-oauth-user
Description: Called by the Proxy after OAuth/OIDC login to resolve the user and their application access. Internal endpoint — calls must carry the proxy shared secret.
Request Body:
{
"user_id": "user-uuid",
"email": "user@example.com",
"name": "User Name",
"application_id": "app-uuid"
}Response:
{
"valid": true,
"user": {
"id": "user-uuid",
"email": "user@example.com",
"name": "User Name",
"username": "username",
"is_machine_user": false,
"is_personal_token": false
},
"tenant": { "id": "tenant-uuid", "name": "Tenant Name" }
}CLI Login (Device Grant) — planned
Not yet implemented
POST /api/cli/login/start and POST /api/cli/login/poll do not exist — there is no /api/cli route on the Manager at all. pfy login only saves a manually-issued --server/--token pair; create the PAT in the Manager UI. Note also that the Manager has no rate limiting anywhere, and the general audit log is never written. See Known Issues.
Planned description: browser login for pfy — a device-authorization grant ending in a Manager-minted PAT. start would take {public_key, token_name} and return {session_id, verification_uri, user_code, interval, expires_at}; the user would approve on a consent page (behind the proxy's OIDC policy, showing the user code, token name, machine, and IP); poll would return authorization_pending / slow_down / denied / expired, or — exactly once — the PAT sealed to the CLI's public key. See the CLI login docs.
Frontend Configuration
Endpoint: GET /api/frontend-config/:application_id/:tenant_id
Description: Retrieves the merged frontend configuration for an application as one tenant sees it (cascade project → tenant → tenant2application), including themes, locales, and enabled modules. Returns 404 if the application is not enabled for the tenant. Used by the Proxy for window.__PRODUCTIFY__ injection and polled by backends for server-side module enablement.
Tenant-less route is deprecated
The old GET /api/frontend-config/:application_id (project + application defaults only) is kept for one release and deprecated since the Wave 3 re-architecture. Move consumers to the tenant-qualified route above. See the Migration Guide.
Response:
{
"config": {
"enabled_modules": ["module1", "module2"],
"enabled_themes": ["light", "dark"],
"default_theme": "light",
"enabled_locales": ["en", "hu"],
"default_locale": "en",
"key_value_pairs": [{ "key": "feature_flag", "value": "true" }]
},
"project_id": "project-uuid"
}Language Pack Download
Endpoint: GET /language-packs/:applicationId/:locale
Description: Returns the language pack for an application's project and the given locale, with flat dot.separated keys expanded into a nested messages object. Used by the Proxy/frontend.
Response:
{
"locale": "en-US",
"messages": {
"app": {
"title": "Welcome"
}
}
}Pilet Feed
Endpoint: GET /pilet-feed/:applicationId
Description: Returns the Piral-compatible pilet feed for an application. This endpoint is used by the frontend to discover and load pilets (microfrontends). Only pilets that are enabled and listed in the application's merged EnabledModules frontend configuration are included.
Authentication: None required (public endpoint)
Response:
{
"items": [
{
"name": "@my-org/my-pilet",
"version": "1.0.0",
"link": "/pilets/project-uuid/@my-org/my-pilet/1.0.0",
"hash": "sha256-abcd1234...",
"spec": "v2"
}
]
}Pilet Package Management
List Pilets
Endpoint: GET /pilets/:projectId
Description: Lists all pilet packages for a project.
Authentication: Bearer Token (Personal Access Token or User Session)
Query Parameters:
enabled(optional): Filter by enabled status (true/false)
Response:
{
"packages": [
{
"id": "pilet-uuid",
"name": "@my-org/my-pilet",
"version": "1.0.0",
"description": "My pilet description",
"size": 123456,
"hash": "sha256-abcd1234...",
"enabled": true,
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-02T00:00:00Z"
}
],
"count": 1
}Upload Pilet
Endpoint: POST /pilets/:projectId/:name/:version
Description: Uploads a new pilet package or updates an existing one.
Authentication: Bearer Token (Personal Access Token or User Session)
Request:
- Content-Type:
multipart/form-data - File field:
file(the pilet package, e.g..tgzor.js) - Optional query parameter:
description(string)
Response:
{
"id": "pilet-uuid",
"name": "@my-org/my-pilet",
"version": "1.0.0",
"size": 123456,
"hash": "sha256-abcd1234...",
"message": "Package uploaded successfully"
}Download Pilet
Endpoint: GET /pilets/:projectId/:name/:version
Description: Downloads a pilet package file (served inline for direct loading).
Authentication: Bearer Token (Personal Access Token or User Session)
Response: Binary package content
Headers:
Content-Type: the package's stored content type (e.g.application/gziporapplication/javascript)Content-Length: package size in bytesX-Pilet-Hash: SHA256 hash of the package contentCache-Control: public, max-age=31536000, immutable
Delete Pilet
Endpoint: DELETE /pilets/:projectId/:name/:version
Description: Soft-deletes a pilet package by disabling it.
Authentication: Bearer Token (Personal Access Token or User Session)
Response:
{
"message": "Package disabled successfully"
}ESB (Enterprise Service Bus) Endpoints
Endpoint: ANY /esb/:applicationId/*endpointPath
Description: Routes requests to configured endpoints through the ESB. Supports all HTTP methods (GET, POST, PUT, DELETE, etc.). The endpoint is selected by matching the request path (the part after the application ID) against the endpoint's consumeURL (exact match), and the machine user must have an enabled access relation (MachineUser2Endpoint) to that endpoint. If a transformation template is configured, the request body is transformed before forwarding, and the call is recorded in the ESB audit log according to the endpoint's logLevel.
Authentication: Bearer Token (Machine User)
Example:
# Request to ESB
GET /esb/app-uuid/api/users
Authorization: Bearer <machine-user-token>
# Routed to the enabled endpoint of the application
# whose consumeURL exactly matches "/api/users"Error responses: 404 if no enabled endpoint matches, 403 if the machine user has no access to the endpoint, 502 if forwarding to the target fails.
Error Handling
GraphQL Errors
GraphQL errors follow the standard GraphQL error structure:
{
"errors": [
{
"message": "ent: settings not found",
"path": ["settings"]
}
],
"data": null
}REST API Errors
REST endpoints return HTTP status codes with an error message:
{
"error": "Invalid credentials"
}Common HTTP Status Codes:
200- Success400- Bad Request401- Unauthorized403- Forbidden404- Not Found500- Internal Server Error
Rate Limiting
The credential-validation endpoints (/api/validate-*) are rate limited, with temporary lockout on repeated failures. There are no general API rate limits; for production deployments, it's recommended to implement rate limiting at the reverse proxy level (e.g., using Caddy or nginx).
Best Practices
1. Use Filtering and Pagination
Always use pagination for list queries to avoid performance issues:
query {
projects(
filters: [{ field: "name", value: "prod" }]
order: { field: "createdAt", direction: DESC }
pagination: { limit: 20, offset: 0 }
) {
id
name
}
}2. Request Only Required Fields
GraphQL allows you to request only the fields you need:
query {
project(slug: "my-project") {
id
name
# Don't fetch relations unless needed
}
}3. Secure Token Storage
- Never commit tokens to version control
- Use environment variables or secret management systems
- Rotate tokens regularly
- Use expiration dates for PATs
4. Handle Errors Gracefully
Always check for errors in GraphQL responses:
const response = await fetch("/query", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ query, variables }),
});
const { data, errors } = await response.json();
if (errors) {
// Handle errors
console.error("GraphQL errors:", errors);
}5. Use Machine Users for Services
For backend services and integrations, always use machine users instead of personal access tokens. Machine users provide:
- Tenant-scoped access
- No expiration (unless disabled)
- Better audit trails
- Service account semantics
Examples
Complete Project Setup
# 1. Create project
mutation {
createProject(input: { name: "E-commerce Platform", slug: "ecommerce" }) {
id
}
}
# 2. Create tenant
mutation {
createTenant(
projectId: "project-uuid"
input: { name: "Store A", slug: "store-a", type: run }
) {
id
}
}
# 3. Create application
mutation {
createApplication(
tenantId: "tenant-uuid"
input: {
name: "Storefront"
slug: "storefront"
description: "Customer-facing web application"
}
) {
id
}
}
# 4. Configure settings
mutation {
updateFrontendModuleConfig(
id: "settings-uuid"
input: {
EnabledThemes: ["light", "dark"]
DefaultTheme: "light"
DefaultLocale: "en"
}
) {
id
}
}Trigger-Based Workflow
# 1. Create backend endpoint
mutation {
createEndpoint(
applicationId: "app-uuid"
input: {
name: "Order Processor"
description: "Processes orders"
consumeURL: "/api/orders/process"
url: "https://backend.example.com/process-orders"
enabled: true
authType: Bearer
}
) {
id
}
}
# 2. Create trigger
mutation {
createTrigger(
applicationId: "app-uuid"
input: {
name: "Hourly Order Processing"
description: "Processes orders hourly"
cronExpression: "0 * * * *"
runKey: "hourly-orders"
enabled: true
}
) {
id
}
}
# 3. Register backend for callbacks
# (REST API call with machine user token)
POST /api/machine/register-backend
{
"application_id": "app-uuid",
"callback_url": "https://backend.example.com/triggers/callback"
}API Versioning
The GraphQL API is unversioned but follows these principles:
- Additive changes - New fields and types are added without breaking existing queries
- Deprecation - Fields are marked
@deprecatedbefore removal - No breaking changes - Existing fields maintain their contracts
REST endpoints are currently unversioned.