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/apiAuthentication
The machine endpoints (/api/machine/*) require machine user credentials:
Bearer Token:
Authorization: Bearer <token>Basic Authentication:
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:
POST /api/validate-proxy-authAuthentication: Proxy shared secret (requests not originating from the proxy are rejected)
Request Body:
{
"authorization_header": "Bearer <token>",
"application_id": "application-uuid"
}Response (Success):
{
"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):
{
"valid": false,
"message": "Invalid or expired authentication credentials"
}Check Machine User Auth
Verifies the current machine user authentication status.
Endpoint:
GET /api/machine/checkAuthentication: Bearer Token (Machine User) - Required
Response:
{
"authenticated": true,
"machine_user_id": "machine-user-uuid",
"tenant_id": "tenant-uuid",
"message": "Authentication successful"
}Example:
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:
POST /api/machine/register-backendAuthentication: Bearer Token (Machine User) - Required
Request Body:
{
"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:
{
"success": true,
"registered_id": "backend-uuid",
"token": "callback-auth-token",
"message": "Backend registered successfully",
"tenant_id": "tenant-uuid",
"application_id": "application-uuid"
}Example:
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 successful201 Created- Resource created successfully
Client Error Codes
400 Bad Request- Invalid request format or parameters401 Unauthorized- Missing or invalid authentication403 Forbidden- Insufficient permissions404 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:
{
"error": "Error message"
}The registration endpoint reports errors in its response envelope instead:
{
"success": false,
"message": "Machine user not authenticated"
}Usage Patterns
Machine User Registration Flow
#!/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:
// 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:
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:
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:
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
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
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
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
- GraphQL API Integration - Primary API for resource management
- Backend Registration - Detailed trigger integration guide
- Machine Users - Service account authentication
- API Reference - Complete API documentation