Skip to content

Transport Layers ​

Grant exposes two API transports that share the same backend (handlers, services, repositories):

TransportEndpointDocumentation
REST/api/*Swagger UI
GraphQL/graphqlApollo Sandbox

Both accept Authorization: Bearer <token> for authentication and use the same scope: { tenant, id } pattern for multi-tenancy.

When to Use Which ​

Use caseRecommendedWhy
OAuth flows (GitHub login)RESTRequires browser redirects
CI/CD scripts, curlRESTSimpler to call from shell
Swagger explorationRESTInteractive docs out of the box
Token exchange (API keys)RESTPOST /api/auth/token is REST-only
JWKS discoveryRESTStandard .well-known endpoint
Web dashboardGraphQLField selection, batching, Apollo Client
Complex nested queriesGraphQLQuery exactly the fields you need
Code generationGraphQLTypes from @grantjs/schema codegen

Endpoint Parity ​

Most operations are available in both transports. A few are REST-only due to their nature (browser redirects, file downloads, well-known discovery). No operations are GraphQL-only.

REST-Only Endpoints ​

EndpointPurpose
GET /api/auth/githubInitiate GitHub OAuth — redirects browser to GitHub
GET /api/auth/github/callbackHandle GitHub OAuth callback — redirect handler
POST /api/auth/cli-callbackExchange one-time CLI code for session tokens
POST /api/auth/tokenExchange API key credentials for JWT
POST /api/auth/is-authorizedAuthorization check (used by @grantjs/server SDK)
GET /api/me/exportGDPR data export (returns file download)
GET /.well-known/jwks.jsonJWKS public key discovery (+ scoped variants)
GET /api/signing-keysList signing keys for a project
POST /api/signing-keys/rotateRotate a project signing key

Shared Operations ​

All CRUD operations for these resources are available in both REST and GraphQL:

Organizations, Projects, Users, Roles, Groups, Permissions, Resources, Tags, API Keys, Organization Invitations, Organization Members, Webhook Subscriptions (project-scoped), and Me notifications / preferences.

Authentication mutations (login, register, refresh, logout, verify email, password reset) are also available in both.

Scoping ​

Both transports use the same scope model. The only difference is how scope is passed:

REST — Query parameters for reads, request body for writes:

bash
# GET (query params)
GET /api/roles?scopeId=<orgId>:<projectId>&tenant=organizationProject

# POST (body)
POST /api/roles
{ "name": "Editor", "scope": { "tenant": "organizationProject", "id": "<orgId>:<projectId>" } }

GraphQL — Always in the input argument:

graphql
mutation {
  createRole(
    input: { name: "Editor", scope: { tenant: organizationProject, id: "<orgId>:<projectId>" } }
  ) {
    id
    name
  }
}

Authentication ​

Both transports accept the same JWT token:

http
Authorization: Bearer <accessToken>

GraphQL additionally supports cookie-based authentication (HttpOnly refresh token cookie) for the web dashboard. The REST API uses the same cookie mechanism for browser-based sessions.

Error Formats ​

Errors carry the same code values across both transports, but the response envelope differs:

REST:

json
{
  "error": "User not found",
  "code": "NOT_FOUND"
}

GraphQL:

json
{
  "data": null,
  "errors": [
    {
      "message": "User not found",
      "extensions": { "code": "NOT_FOUND" }
    }
  ]
}

See Error Handling for the full reference.

GraphQL Sandbox ​

In non-production environments, the GraphQL endpoint at /graphql serves the Apollo Sandbox — an embedded IDE for building and testing queries.

To authenticate in the sandbox:

  1. Open http://localhost:4000/graphql
  2. Click the Headers tab at the bottom
  3. Add: { "Authorization": "Bearer <accessToken>" }
  4. Write and execute queries/mutations

Introspection

Schema introspection is enabled by default in non-production environments (APOLLO_INTROSPECTION=true). It is automatically disabled in production.

Apollo also enforces selection-set depth (GRAPHQL_MAX_DEPTH, default 15) and complexity (GRAPHQL_MAX_COMPLEXITY, default 10000). List limit arguments multiply child cost and are themselves capped at PAGINATION_MAX_PAGE_SIZE (default 100). EntityRepository.query clamps the same way; limit: -1 on public lists becomes the max page size. Organization member lists page in SQL (members ∪ invitations, then batch-hydrate the page) and reject public -1. Project-scoped user lists load project_users only for the page’s user ids. Internal complete-set loads (MFA factors, JWKS keys, field-resolver hydration) opt out explicitly.

GraphQL Operations ​

The GraphQL API provides queries and mutations covering the shared resources above (exact counts drift as the schema grows). Key operation groups:

GroupQueriesMutations
Authenticationme, isAuthorizedlogin, register, refreshSession, verifyEmail, resetPassword, ...
Organizationsorganizations, organizationMembers, organizationInvitationscreateOrganization, inviteMember, acceptInvitation, ...
ProjectsprojectscreateProject, updateProject, deleteProject
RBACroles, groups, permissions, resourcesCRUD for each
UsersuserscreateUser, updateUser, deleteUser
API KeysapiKeys, signingKeyscreateApiKey, exchangeApiKey, rotateSigningKey, ...
WebhookswebhookSubscriptions, webhookSubscription, webhookDeliveriescreateWebhookSubscription, updateWebhookSubscription, replayWebhookDelivery, ...
TagstagscreateTag, updateTag, deleteTag
MemyUserSessions, myNotifications, myNotificationPreferences, …changeMyPassword, markMyNotificationRead, setMyNotificationPreference, …

The full schema is available via introspection or through the @grantjs/schema package.


Related:

Released under the MIT License.