Skip to content

REST API

In addition to the GraphQL API, the Manager provides REST endpoints for specific operations, primarily for machine user authentication and backend registration.

Base URL

https://manager.example.com/api

Authentication

The machine endpoints (/api/machine/*) require machine user credentials:

Bearer Token:

http
Authorization: Bearer <token>

Basic Authentication:

http
Authorization: Basic <base64(username:password)>

Endpoints

Validate Proxy Authentication

Validates credentials forwarded by the Proxy (Personal Access Tokens, machine user Bearer tokens, and machine user Basic auth). Used internally by the Proxy; it is not intended for application code.

Endpoint:

http
POST /api/validate-proxy-auth

Authentication: Proxy shared secret (requests not originating from the proxy are rejected)

Request Body:

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

Response (Success):

json
{
  "valid": true,
  "user": {
    "id": "user-or-machine-user-uuid",
    "email": "user@example.com",
    "name": "User Name",
    "username": "username",
    "is_machine_user": false,
    "is_personal_token": true
  },
  "tenant": {
    "id": "tenant-uuid",
    "name": "Tenant Name"
  },
  "application": {
    "has_access": true,
    "role": "admin"
  }
}

Response (Invalid):

json
{
  "valid": false,
  "message": "Invalid or expired authentication credentials"
}

Check Machine User Auth

Verifies the current machine user authentication status.

Endpoint:

http
GET /api/machine/check

Authentication: Bearer Token (Machine User) - Required

Response:

json
{
  "authenticated": true,
  "machine_user_id": "machine-user-uuid",
  "tenant_id": "tenant-uuid",
  "message": "Authentication successful"
}

Example:

bash
curl -X GET https://manager.example.com/api/machine/check \
  -H "Authorization: Bearer <machine-user-token>"

Register Backend

Registers a backend service to receive trigger execution callbacks.

Endpoint:

http
POST /api/machine/register-backend

Authentication: Bearer Token (Machine User) - Required

Request Body:

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

version is optional. Re-registering an existing backend updates its callback URL, version, and heartbeat timestamp while keeping the same token.

Response:

json
{
  "success": true,
  "registered_id": "backend-uuid",
  "token": "callback-auth-token",
  "message": "Backend registered successfully",
  "tenant_id": "tenant-uuid",
  "application_id": "application-uuid"
}

Example:

bash
curl -X POST https://manager.example.com/api/machine/register-backend \
  -H "Authorization: Bearer <machine-user-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "application_id": "application-uuid",
    "callback_url": "https://payment.example.com/triggers/callback"
  }'

Backend Heartbeat

No dedicated heartbeat endpoint

There is no PUT /api/machine/backend/:id/heartbeat route. The Manager exposes only POST /api/machine/register-backend and GET /api/machine/check under /api/machine.

Heartbeating works by re-calling registration: @productifyfw/node-express periodically POSTs /api/machine/register-backend again, which refreshes the record. That is the supported liveness mechanism.


Unregister Backend

Documented, called by the client, but not implemented

There is no DELETE /api/machine/backend/:id route on the Manager. @productifyfw/node-express's unregister() calls this URL anyway; the request comes back non-2xx, so unregistration always fails — it throws (and rethrows past onError if one was supplied), and on the process-exit cleanup path the rejection is caught and console.error'd. It fails loudly, but the backend registration is left behind either way and continues to be a trigger-callback target until it is otherwise replaced.

See Known Issues.

The intended design: removing a backend registration so it no longer receives trigger callbacks, authenticated as the machine user, returning 404 if the backend does not exist or does not belong to the caller.


Error Responses

All REST endpoints return standard HTTP status codes:

Success Codes

  • 200 OK - Request successful
  • 201 Created - Resource created successfully

Client Error Codes

  • 400 Bad Request - Invalid request format or parameters
  • 401 Unauthorized - Missing or invalid authentication
  • 403 Forbidden - Insufficient permissions
  • 404 Not Found - Resource not found

Server Error Codes

  • 500 Internal Server Error - Server-side error occurred

Error Response Format

Most endpoints return a simple error object:

json
{
  "error": "Error message"
}

The registration endpoint reports errors in its response envelope instead:

json
{
  "success": false,
  "message": "Machine user not authenticated"
}

Usage Patterns

Machine User Registration Flow

bash
#!/bin/bash

# 1. Create machine user (via GraphQL)
RESPONSE=$(curl -X POST https://manager.example.com/query \
  -H "Authorization: Bearer $USER_PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation { createMachineUserWithCredentials(tenantId: \"'$TENANT_ID'\", input: { name: \"Backend Service\", username: \"backend\", hashedKey: \"__GENERATE_TOKEN__\", enabled: true }) { generatedToken machineUser { id } } }"
  }')

MACHINE_TOKEN=$(echo $RESPONSE | jq -r '.data.createMachineUserWithCredentials.generatedToken')

# 2. Verify authentication
curl -X GET https://manager.example.com/api/machine/check \
  -H "Authorization: Bearer $MACHINE_TOKEN"

# 3. Register backend for triggers
curl -X POST https://manager.example.com/api/machine/register-backend \
  -H "Authorization: Bearer $MACHINE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "application_id": "'$APPLICATION_ID'",
    "callback_url": "https://backend.example.com/triggers/callback"
  }'

Health Check Integration

Use the check endpoint for health monitoring:

javascript
// Health check endpoint
app.get("/health", async (req, res) => {
  try {
    const response = await fetch(
      "https://manager.example.com/api/machine/check",
      {
        headers: {
          Authorization: `Bearer ${process.env.MACHINE_TOKEN}`,
        },
      }
    );

    if (response.ok) {
      const data = await response.json();
      res.json({
        status: "healthy",
        authenticated: data.authenticated,
      });
    } else {
      res.status(503).json({ status: "unhealthy" });
    }
  } catch (error) {
    res.status(503).json({
      status: "unhealthy",
      error: error.message,
    });
  }
});

Best Practices

Error Handling

Always handle errors gracefully:

javascript
async function checkMachineUserAuth(token) {
  try {
    const response = await fetch(
      "https://manager.example.com/api/machine/check",
      {
        headers: { Authorization: `Bearer ${token}` },
      }
    );

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    const data = await response.json();
    return data.authenticated;
  } catch (error) {
    console.error("Authentication check failed:", error);
    return false;
  }
}

Retry Logic

Implement retries for transient failures:

python
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

def create_session_with_retries():
    session = requests.Session()

    retry = Retry(
        total=3,
        backoff_factor=1,
        status_forcelist=[500, 502, 503, 504]
    )

    adapter = HTTPAdapter(max_retries=retry)
    session.mount('http://', adapter)
    session.mount('https://', adapter)

    return session

# Use the session
session = create_session_with_retries()
response = session.get(
    'https://manager.example.com/api/machine/check',
    headers={'Authorization': f'Bearer {token}'}
)

Timeout Configuration

Set appropriate timeouts:

javascript
const response = await fetch("https://manager.example.com/api/machine/check", {
  headers: { Authorization: `Bearer ${token}` },
  signal: AbortSignal.timeout(5000), // 5 second timeout
});

Migration from GraphQL

While the GraphQL API provides comprehensive functionality, REST endpoints are provided for specific use cases:

Use REST when:

  • Implementing Proxy authentication
  • Simple machine user validation needed
  • Backend registration for triggers
  • Lightweight health checks

Use GraphQL when:

  • Managing complex resources
  • Querying related data
  • Batch operations
  • Type-safe API interactions

API Client Examples

Node.js

javascript
class ManagerRestClient {
  constructor(baseUrl, token) {
    this.baseUrl = baseUrl;
    this.token = token;
  }

  async checkAuth() {
    const response = await fetch(`${this.baseUrl}/api/machine/check`, {
      headers: { Authorization: `Bearer ${this.token}` },
    });
    return response.json();
  }

  async registerBackend(applicationId, callbackUrl, version) {
    const response = await fetch(
      `${this.baseUrl}/api/machine/register-backend`,
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${this.token}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          application_id: applicationId,
          callback_url: callbackUrl,
          version,
        }),
      }
    );
    return response.json();
  }
}

// Usage
const client = new ManagerRestClient("https://manager.example.com", token);
await client.checkAuth();

Python

python
import requests

class ManagerRestClient:
    def __init__(self, base_url, token):
        self.base_url = base_url
        self.token = token
        self.headers = {'Authorization': f'Bearer {token}'}

    def check_auth(self):
        response = requests.get(
            f'{self.base_url}/api/machine/check',
            headers=self.headers
        )
        return response.json()

    def register_backend(self, application_id, callback_url, version=''):
        response = requests.post(
            f'{self.base_url}/api/machine/register-backend',
            headers={**self.headers, 'Content-Type': 'application/json'},
            json={
                'application_id': application_id,
                'callback_url': callback_url,
                'version': version
            }
        )
        return response.json()

# Usage
client = ManagerRestClient('https://manager.example.com', token)
client.check_auth()

Go

go
package main

import (
    "bytes"
    "encoding/json"
    "net/http"
)

type ManagerRestClient struct {
    BaseURL string
    Token   string
    Client  *http.Client
}

func (c *ManagerRestClient) CheckAuth() (map[string]interface{}, error) {
    req, _ := http.NewRequest("GET", c.BaseURL+"/api/machine/check", nil)
    req.Header.Set("Authorization", "Bearer "+c.Token)

    resp, err := c.Client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var result map[string]interface{}
    json.NewDecoder(resp.Body).Decode(&result)
    return result, nil
}

func (c *ManagerRestClient) RegisterBackend(applicationID, callbackURL, version string) (map[string]interface{}, error) {
    body := map[string]string{
        "application_id": applicationID,
        "callback_url":   callbackURL,
        "version":        version,
    }

    jsonBody, _ := json.Marshal(body)
    req, _ := http.NewRequest("POST", c.BaseURL+"/api/machine/register-backend", bytes.NewBuffer(jsonBody))
    req.Header.Set("Authorization", "Bearer "+c.Token)
    req.Header.Set("Content-Type", "application/json")

    resp, err := c.Client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var result map[string]interface{}
    json.NewDecoder(resp.Body).Decode(&result)
    return result, nil
}

See Also