Skip to content

GraphQL Integration ​

The GraphQL API at POST /query is how you manage everything in the Manager from code: projects, tenants, applications, endpoints, triggers, settings, users and tokens. It is the same API the Manager UI and the pfy CLI use. Below: authentication, a minimal client in JavaScript, Python and Go, and a few patterns that avoid common mistakes. The full list of operations is in the API Reference.

Complete API Reference

See the API Reference for complete GraphQL schema documentation, all queries, mutations, and detailed examples.

Quick Start ​

Endpoint ​

POST http://your-manager-host:8080/query

GraphQL Playground ​

Interactive schema explorer (development mode only):

GET http://localhost:8080/gql

Authentication ​

Include authentication token in request headers:

http
Authorization: Bearer <your-token>

Use either:

  • a Personal Access Token, sent as a Bearer token, for CLI tools and scripts
  • an OAuth session, when requests come through the Productify proxy

Machine user tokens are not accepted on the GraphQL endpoint; they are limited to the REST (/api/machine/*) and ESB endpoints.

See Authentication for details.

Send /query through the proxy, not straight at port 8080

/query is gated on the proxy shared secret before your PAT is even read, so a request that reaches the Manager's port directly is rejected with a 401 whose JSON body says the request did not come through the trusted proxy, no matter how good the token is. Point clients at the proxy-fronted host (https://manager.example.com/query), which validates the PAT and adds the X-Proxy-Auth and identity headers for you.

The localhost:8080 examples below therefore only work against a local development Manager running with PFY_ENV=development and PFY_DEV_USER_OVERRIDE_ENABLED=true, which is the one supported way to run without a proxy in front. See Configuration.

Client Setup ​

JavaScript/TypeScript ​

javascript
const graphqlClient = async (query, variables = {}) => {
  const response = await fetch("http://localhost:8080/query", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.MANAGER_TOKEN}`,
    },
    body: JSON.stringify({ query, variables }),
  });

  const { data, errors } = await response.json();

  if (errors) {
    throw new Error(errors[0].message);
  }

  return data;
};

Python ​

python
import requests
import os

def graphql_client(query, variables=None):
    response = requests.post(
        'http://localhost:8080/query',
        json={'query': query, 'variables': variables or {}},
        headers={
            'Authorization': f'Bearer {os.getenv("MANAGER_TOKEN")}'
        }
    )

    result = response.json()

    if 'errors' in result:
        raise Exception(result['errors'][0]['message'])

    return result['data']

Go ​

go
package main

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

type GraphQLRequest struct {
    Query     string                 `json:"query"`
    Variables map[string]interface{} `json:"variables,omitempty"`
}

func GraphQLClient(query string, variables map[string]interface{}) (map[string]interface{}, error) {
    reqBody, _ := json.Marshal(GraphQLRequest{
        Query:     query,
        Variables: variables,
    })

    req, _ := http.NewRequest("POST", "http://localhost:8080/query", bytes.NewBuffer(reqBody))
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Authorization", "Bearer "+os.Getenv("MANAGER_TOKEN"))

    client := &http.Client{}
    resp, err := 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
}

Common Patterns ​

Query with Variables ​

javascript
const query = `
  query GetTenant($projectId: ID!, $slug: String!) {
    tenant(projectId: $projectId, slug: $slug) {
      id
      name
      project {
        applications {
          id
          name
        }
      }
    }
  }
`;

const data = await graphqlClient(query, {
  projectId: "project-123",
  slug: "my-tenant",
});

Create Resource ​

Applications belong to a project, not to a tenant: you create one against a project and then enable it per tenant. See Multi-tenancy.

javascript
const mutation = `
  mutation CreateApplication($projectId: ID!, $input: CreateApplicationInput!) {
    createApplication(projectId: $projectId, input: $input) {
      id
      name
      slug
    }
  }
`;

const result = await graphqlClient(mutation, {
  projectId: "project-uuid",
  input: {
    name: "My App",
    slug: "my-app",
    description: "Application description",
  },
});

Error Handling ​

javascript
try {
  const data = await graphqlClient(query, variables);
  console.log("Success:", data);
} catch (error) {
  console.error("GraphQL Error:", error.message);
  // Error messages are plain text, e.g. "ent: tenant not found" or
  // "unauthorized: ...". Match on the message when you need to branch.
  if (error.message.includes("not found")) {
    // Resource not found
  } else if (error.message.includes("unauthorized")) {
    // The caller may not do this
  }
}

An HTTP 401 (not a GraphQL error) means the request never reached the API: the PAT was rejected or the request bypassed the proxy. The body is JSON with an error field that names the reason.

Best Practices ​

Use Variables ​

Always use variables instead of hardcoding values:

javascript
// [NO] Avoid hardcoding
const query = `
  query {
    tenant(projectId: "abc-123", slug: "my-tenant") { name }
  }
`;

// [OK] Use variables
const query = `
  query GetTenant($projectId: ID!, $slug: String!) {
    tenant(projectId: $projectId, slug: $slug) { name }
  }
`;

const data = await graphqlClient(query, {
  projectId: "abc-123",
  slug: "my-tenant",
});

Request Only Needed Fields ​

GraphQL allows precise field selection - use it:

javascript
// [NO] Over-fetching
const query = `
  query {
    applications(projectSlug: "my-project", filters: [], order: {...}, pagination: {...}) {
      id name slug description createdAt updatedAt
      project { id name slug createdAt updatedAt }
      endpoints { id name url enabled createdAt }
      triggers { id name cronExpression enabled }
    }
  }
`;

// [OK] Request only what you need
const query = `
  query {
    applications(projectSlug: "my-project", filters: [], order: {...}, pagination: {...}) {
      id
      name
    }
  }
`;

Use Fragments for Reusability ​

javascript
const fragments = `
  fragment ApplicationFields on Application {
    id
    name
    slug
  }
`;

const query = `
  ${fragments}
  query GetApplications($projectSlug: String!) {
    applications(projectSlug: $projectSlug, filters: [], order: {...}, pagination: {...}) {
      ...ApplicationFields
    }
  }
`;

Handle Pagination ​

javascript
async function getAllApplications(projectSlug) {
  const allApps = [];
  let offset = 0;
  const limit = 50; // must be one of 1, 10, 20, 25, 50, 100

  while (true) {
    const data = await graphqlClient(
      `
      query GetApplications($projectSlug: String!, $limit: Int!, $offset: Int!) {
        applications(
          projectSlug: $projectSlug
          filters: []
          order: { field: "name", direction: ASC }
          pagination: { limit: $limit, offset: $offset }
        ) {
          id
          name
        }
      }
    `,
      { projectSlug, limit, offset }
    );

    if (data.applications.length === 0) break;

    allApps.push(...data.applications);
    offset += limit;
  }

  return allApps;
}

GraphQL Tools ​

Apollo Client (JavaScript) ​

bash
npm install @apollo/client graphql
javascript
import { ApolloClient, InMemoryCache, gql } from "@apollo/client";

const client = new ApolloClient({
  uri: "http://localhost:8080/query",
  cache: new InMemoryCache(),
  headers: {
    authorization: `Bearer ${process.env.MANAGER_TOKEN}`,
  },
});

const { data } = await client.query({
  query: gql`
    query GetTenants($projectSlug: String!) {
      tenants(projectSlug: $projectSlug, filters: [], order: {...}, pagination: {...}) {
        id
        name
      }
    }
  `,
  variables: { projectSlug: "my-project" },
});

urql (React) ​

bash
npm install urql graphql
javascript
import { createClient, useQuery } from "urql";

const client = createClient({
  url: "http://localhost:8080/query",
  fetchOptions: {
    headers: {
      authorization: `Bearer ${process.env.MANAGER_TOKEN}`,
    },
  },
});

function TenantsList() {
  const [result] = useQuery({
    query: `
      query GetTenants($projectSlug: String!) {
        tenants(projectSlug: $projectSlug, filters: [], order: {...}, pagination: {...}) {
          id name
        }
      }
    `,
    variables: { projectSlug: "my-project" },
  });

  return <div>{/* render */}</div>;
}

See Also ​