Partner Requirements

Experimental Feature

This experimental feature is available to try out before it’s fully released or ready for production. We recommend using it in non-critical sites and non-production environments only. Learn more in our Netlify Labs doc.

This document outlines the requirements for integrating with Netlify Graph. When partners add their service to Netlify Graph, developers get access to two key features:

  1. API Authentication
  2. Graph Explorer integration

Both features can be integrated separately. However, we recommend integrating both, as they work best together.

# API Authentication

The API Authentication experience enables developers to easily build workflows that use partner APIs without writing the authentication code from scratch. By abstracting the OAuth complexity, developers can log in to the partner service with Netlify Graph and re-use the partner API token whenever necessary. No need to worry about refreshes, whether the right scopes are selected, or whether the user needs to be re-authenticated - it’s all handled automatically by Netlify Graph.

# Endpoints

To integrate with Netlify Graph, partners must implement the Open ID Connect (OIDC) standard. OIDC can be thought of as an extension to the OAuth2 standard and it’s feasible to build OIDC features on top of a working OAuth2 setup.

In practical terms, this means implementing the following endpoints:

Partners need to ensure that they are implementing the standard in the linked specifications.

# Data structures

This section outlines the Netlify Graph API endpoint response data structures.

# Token endpoint

The Token endpoint should return a JSON-encoded result similar to the example below. All fields in the following example are required. The values in the example are placeholders.

{
   "access_token": "SlAV32hkKG",
   "token_type": "Bearer",
   "refresh_token": "8xLOxBtZp8",
   "expires_in": 3600,
   "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ.ewogImlzcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5NzAKfQ.ggW8hZ1EuVLuxNuuIJKX_V8a_OMXzR0EHR9R6jgdqrOOF4daGU96Sr_P6qJp6IcmD3HP99Obi1PRs-cwh3LO-p146waJ8IhehcwL7F09JdijmBqkvPeB2T9CJNqeGpe-gccMg4vfKjkM8FcGvnzZUN4_KSP0aAp1tOJ1zZwgjxqGByKHiOtX7TpdQyHE5lcMiKPXfEIQILVq0pc_E2DzL7emopWoaoZTF_m0_N0YzFC6g6EJbOEoRoS     K5hoDalrcvRYLSrQAZZKflyuVCyixEoV9GfNQC3_osjzw2PAithfubEEBLuVVk4XUVrWOLrLl0nx7RkKU8NXNHq-rvKMzqg"
  } 

# UserInfo endpoint

The UserInfo response must contain a sub value. It can also contain other relevant details for an application, such as name, email, or avatar URL. Here’s an example of what this could look like:

type UserInfoResponse = {
  /**
  * The id of the authenticated user (the "subject")
  */
  sub: string
}

Example UserInfo endpoint response that contains the required sub field and some additional (optional) user information:

{
   "sub": "248289761001",
   "name": "Jane Doe",
   "given_name": "Jane",
   "family_name": "Doe",
   "preferred_username": "j.doe",
   "email": "janedoe@example.com",
   "picture": "http://example.com/janedoe/me.jpg"
 }

# Tokens

Netlify Graph expects access tokens to be relatively short-lived (hours to days, not months or infinite) and to be able to refresh them after they expire using a one-time refresh token. Upon refreshing, Netlify Graph expects a new access token and a new refresh token.

# Scope description

To help users understand what authentication is available in the API, the Netlify Graph API needs a list of scopes with the following structure:

type Scope = {
  /**
  * Actual programmatic scope specified during OAuth dance
  */
  scope: string;
  /**
  * Description as to the purpose of the scope / what it grants access to
  */
  description: string;
  /**
  * Human-friendly title of the scope
  */
  title: string;
  /**
  * A category to group multiple scopes under
  */
  category: string;
  /**
  * Whether the scope should be specified by default
  */
  isDefault: boolean;
  /**
  * Whether the scope is required to access the service
  * If `true`, `isDefault` must also be `true`.
  *
  * If this scope is required in order to retrieve the
  * logged-in user information associated with this token
  * (primarily their id in your service), then this field
  * must be `true`.
  */
  isRequired: boolean;
}

For example, here is how Spotify outlines the scopes in their own API:

[
  {
    "scope": "ugc-image-upload",
    "display": "ugc-image-upload",
    "description": "Write access to user-provided images.",
    "title": "Upload images to Spotify on your behalf.",
    "category": "Images",
    "isDefault": false,
    "isRequired": false,
  },
  {
    "scope": "user-read-recently-played",
    "display": "user-read-recently-played",
    "description": "Read access to a user’s recently played tracks.",
    "title": "Access your recently played items.",
    "category": "Listening History",
    "isDefault": true,
    "isRequired": false,
  },
  {
    "scope": "user-read-playback-state",
    "display": "user-read-playback-state",
    "description": "Read access to a user’s player state.",
    "title": "Read your currently playing content and Spotify Connect devices information.",
    "category": "Spotify Connect",
    "isDefault": true,
    "isRequired": false,
  }
]

# Graph Explorer integration

By integrating with the Graph Explorer experience, partners can enable developers to interact with their Graph API surface through Netlify Graph, and combine the information from their API with any of the supported services that are already part of the Netlify Graph.

There are two types of compatible Graph Explorer integration - ad-hoc and standard.

# Standard

To make the integration ready for production, partners will need to ensure that additional requirements are met:

  • Global Object Identification (GID) implemented. We need this for the Node Interface https://dev.to/zth/the-magic-of-the-node-interface-4le1.
  • Cursor Connection Spec implemented. It standardizes pagination, and enables us to generate automatic functions like query.users.pageInfo.hasNextPage ? query.users.fetchMore() : (). We can even automatically nest pagination like this example, but that requires both the GID+Node Interface and the Cursor Connection Spec.
  • Any top-level fields under query should be resource-oriented, and not action-oriented. For example:
    • Bad: getUser, getUserById, allUsers, getAllUsers
    • Good: user(id: $userId), user(email: $email), users(first: $first, after: $cursor, filter: {createdAfter: $date, orderBy: {field: CREATED_AT, direction: DESC})

The above requires more work on the partner side, but as a result no developer ever writes pagination code (even nested pagination), Netlify infrastructure can poll stale-APIs efficiently without missing items, and it enables building more advanced future capabilities.

# Ad-hoc

Ad-hoc integrations are not added to Netlify Graph’s global index, which means that they’re not visible to Netlify Graph users. Ad-hoc integrations are only used for testing and validation, and are not yet ready for adoption by developers. It’s a great way to get started and make sure that partners have the foundational building blocks before moving further.

To create an ad-hoc integration, partners need to ensure that their GraphQL endpoints and data meet the following requirements:

  • Must implement GraphQL schema introspection.
  • Naming requirements:
    • All field names normalized camelCase. For example, not User.first_name. We can normalize this on our end automatically to User.firstName, but it may cause unexpected issues for API partners (and then, consequently, to developers).
    • No fields can start with a capital letter.
    • All GraphQL types must start with a capital letter and use PascalCase naming structure.