Skip to content

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 the pfy CLI 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:

http
Authorization: Bearer <your-token>

Creating a PAT:

graphql
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):

http
Authorization: Bearer <machine-user-token>

Basic Authentication:

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

graphql
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

graphql
input Filter {
  field: String!
  value: String!
}

Order

graphql
input Order {
  field: String!
  direction: OrderDirection!
}

enum OrderDirection {
  ASC
  DESC
}

Pagination

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

graphql
enum EndpointAuthType {
  Basic # HTTP Basic Authentication
  Bearer # Bearer Token Authentication
  None # No authentication required
}

Message Types:

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

graphql
enum EndpointLogLevel {
  NONE # No logging
  BASIC # Log metadata only (headers, status, timing)
  FULL # Log full request/response bodies
}

Queries

Projects

List Projects

graphql
query {
  projects(
    filters: []
    order: { field: "createdAt", direction: DESC }
    pagination: { limit: 10, offset: 0 }
  ) {
    id
    name
    slug
    createdAt
  }
}

Get Project by Slug

graphql
query {
  project(slug: "my-project") {
    id
    name
    slug
    tenants {
      id
      name
    }
  }
}

Tenants

List Tenants

graphql
query {
  tenants(
    projectSlug: "my-project"
    filters: []
    order: { field: "name", direction: ASC }
    pagination: { limit: 20, offset: 0 }
  ) {
    id
    name
    slug
    type
  }
}

Get Tenant

graphql
query {
  tenant(projectId: "project-uuid", slug: "tenant-slug") {
    id
    name
    slug
    applications {
      id
      name
    }
  }
}

Applications

List Applications

graphql
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

graphql
query {
  application(
    projectId: "project-uuid"
    tenantId: "tenant-uuid"
    slug: "app-slug"
  ) {
    id
    name
    slug
    description
    triggers {
      id
      name
      cronExpression
      enabled
    }
  }
}

Settings

Get Settings

graphql
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

graphql
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

graphql
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

graphql
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

graphql
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

graphql
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)

graphql
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

graphql
query {
  me {
    id
    externalName
    externalUsername
    externalEmail
    role
  }
}

List Users

graphql
query {
  users(
    filters: []
    order: { field: "externalName", direction: ASC }
    pagination: { limit: 20, offset: 0 }
  ) {
    id
    externalName
    externalUsername
    externalEmail
    role
  }
}

Search Provider Users

graphql
query {
  userProviderSearch(name: "john") {
    id
    username
    email
    displayName
    disabled
  }
}

User Relationships

List User-Application Relations

graphql
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

graphql
query {
  user2application(application: "app-uuid", id: "relation-uuid") {
    id
    role
    user {
      externalUsername
    }
  }
}

List User-Project Relations

graphql
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

graphql
query {
  user2project(project: "project-uuid", id: "relation-uuid") {
    id
    role
    user {
      externalUsername
    }
  }
}

List User-Tenant Relations

graphql
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

graphql
query {
  user2tenant(tenant: "tenant-uuid", id: "relation-uuid") {
    id
    role
    user {
      externalUsername
    }
  }
}

Machine User to Endpoint Relations

List Machine User-Endpoint Relations

graphql
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

graphql
query {
  machineUser2endpoint(endpoint: "endpoint-uuid", id: "relation-uuid") {
    id
    enabled
  }
}

Audit Logs

List Audit Logs

graphql
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

graphql
query {
  piletPackages(projectId: "project-uuid", enabled: true) {
    packages {
      id
      name
      version
      description
      size
      hash
      enabled
      createdAt
      updatedAt
    }
    count
  }
}

Get Pilet Package

graphql
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

graphql
mutation {
  createProject(input: { name: "My Project", slug: "my-project" }) {
    id
    name
    slug
  }
}

Update Project

graphql
mutation {
  updateProject(
    id: "project-uuid"
    input: { name: "Updated Project Name", slug: "updated-project" }
  ) {
    id
    name
    slug
  }
}

Delete Project

graphql
mutation {
  deleteProject(id: "project-uuid")
}

Tenant Management

Create Tenant

graphql
mutation {
  createTenant(
    projectId: "project-uuid"
    input: { name: "Customer A", slug: "customer-a", type: run }
  ) {
    id
    name
    slug
    type
  }
}

Update Tenant

graphql
mutation {
  updateTenant(id: "tenant-uuid", input: { name: "Updated Customer Name" }) {
    id
    name
  }
}

Delete Tenant

graphql
mutation {
  deleteTenant(id: "tenant-uuid")
}

Application Management

Create Application

graphql
mutation {
  createApplication(
    tenantId: "tenant-uuid"
    input: {
      name: "Web App"
      slug: "web-app"
      description: "Main web application"
    }
  ) {
    id
    name
    slug
  }
}

Update Application

graphql
mutation {
  updateApplication(
    id: "app-uuid"
    input: { description: "Updated description" }
  ) {
    id
    description
  }
}

Delete Application

graphql
mutation {
  deleteApplication(id: "app-uuid")
}

Promote Application

Promote/copy an application version to another tenant (environment):

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

graphql
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

graphql
mutation {
  deleteBackendSettings(id: "backend-settings-uuid")
}

Delete Frontend Settings

graphql
mutation {
  deleteFrontendSettings(id: "frontend-settings-uuid")
}

Update Backend Module Configuration

graphql
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

graphql
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

graphql
mutation {
  createLanguagePack(projectId: "project-uuid", input: { name: "en-US" }) {
    id
    name
  }
}

Update Language Pack Content

graphql
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

graphql
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 /esb requests are matched against this value
  • url - Optional internal URL used for routing
  • authType - Authentication method: Basic, Bearer, or None (default: None)
  • inputMessageType - Expected input format: JSON, XML, or SOAP (optional)
  • outputMessageType - Output format after transformation: JSON, XML, or SOAP (optional)
  • logLevel - ESB logging verbosity: NONE, BASIC, or FULL (default: BASIC)

Update Endpoint

graphql
mutation {
  updateEndpoint(
    id: "endpoint-uuid"
    input: {
      enabled: false
      url: "https://new-backend.example.com/webhook"
      logLevel: FULL
    }
  ) {
    id
    enabled
    url
    logLevel
  }
}

Delete Endpoint

graphql
mutation {
  deleteEndpoint(id: "endpoint-uuid")
}

Trigger Management

Create Trigger

graphql
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

graphql
mutation {
  updateTrigger(
    id: "trigger-uuid"
    input: { enabled: false, cronExpression: "0 2 * * *" }
  ) {
    id
    enabled
    cronExpression
  }
}

Machine User Management

Create Machine User with Token

graphql
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

graphql
mutation {
  updateMachineUser(id: "machine-user-uuid", input: { enabled: false }) {
    id
    enabled
  }
}

Delete Machine User

graphql
mutation {
  deleteMachineUser(id: "machine-user-uuid")
}

Machine User to Endpoint Access

Create Machine User-Endpoint Access

graphql
mutation {
  createMachineUser2Endpoint(
    input: {
      machineUserID: "machine-user-uuid"
      endpointID: "endpoint-uuid"
      enabled: true
    }
  ) {
    id
    enabled
    machineUser {
      username
    }
    endpoint {
      name
    }
  }
}

Update Machine User-Endpoint Access

graphql
mutation {
  updateMachineUser2Endpoint(id: "relation-uuid", input: { enabled: false }) {
    id
    enabled
  }
}

Delete Machine User-Endpoint Access

graphql
mutation {
  deleteMachineUser2Endpoint(id: "relation-uuid")
}

Personal Access Token Management

Create Personal Access Token

graphql
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

graphql
mutation {
  revokePersonalAccessToken(id: "pat-uuid") {
    id
    name
    revoked
  }
}

Delete Personal Access Token

graphql
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

graphql
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

graphql
mutation {
  updateUserProjectRole(id: "relation-uuid", role: readonly) {
    id
    role
  }
}

Remove User from Project

graphql
mutation {
  removeUserFromProject(id: "relation-uuid")
}

Add User to Tenant

graphql
mutation {
  addUserToTenant(tenantID: "tenant-uuid", userID: "user-uuid", role: normal) {
    id
    role
  }
}

Available Roles: readonly, normal, tester, developer, maintainer, owner

Update User Tenant Role

graphql
mutation {
  updateUserTenantRole(id: "relation-uuid", role: developer) {
    id
    role
  }
}

Remove User from Tenant

graphql
mutation {
  removeUserFromTenant(id: "relation-uuid")
}

Add User to Application

graphql
mutation {
  addUserToApplication(
    applicationID: "app-uuid"
    userID: "user-uuid"
    role: normal
  ) {
    id
    role
  }
}

Available Roles: readonly, normal

Update User Application Role

graphql
mutation {
  updateUserApplicationRole(id: "relation-uuid", role: readonly) {
    id
    role
  }
}

Remove User from Application

graphql
mutation {
  removeUserFromApplication(id: "relation-uuid")
}

Pilet Package Management

Update Pilet Package

graphql
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

graphql
mutation {
  enablePiletPackage(
    projectId: "project-uuid"
    name: "@my-org/my-pilet"
    version: "1.0.0"
  ) {
    id
    enabled
  }
}

Disable Pilet Package

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

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

json
{
  "application_id": "app-uuid",
  "callback_url": "https://payment.example.com/triggers/callback",
  "version": "1.2.3"
}

Response:

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

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

json
{
  "authorization_header": "Bearer <token>",
  "application_id": "app-uuid"
}

Response:

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

json
{
  "user_id": "user-uuid",
  "email": "user@example.com",
  "name": "User Name",
  "application_id": "app-uuid"
}

Response:

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

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

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

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

json
{
  "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. .tgz or .js)
  • Optional query parameter: description (string)

Response:

json
{
  "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/gzip or application/javascript)
  • Content-Length: package size in bytes
  • X-Pilet-Hash: SHA256 hash of the package content
  • Cache-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:

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

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

json
{
  "errors": [
    {
      "message": "ent: settings not found",
      "path": ["settings"]
    }
  ],
  "data": null
}

REST API Errors

REST endpoints return HTTP status codes with an error message:

json
{
  "error": "Invalid credentials"
}

Common HTTP Status Codes:

  • 200 - Success
  • 400 - Bad Request
  • 401 - Unauthorized
  • 403 - Forbidden
  • 404 - Not Found
  • 500 - 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:

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

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

javascript
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

graphql
# 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

graphql
# 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 @deprecated before removal
  • No breaking changes - Existing fields maintain their contracts

REST endpoints are currently unversioned.