BackendBackend SDK

Node

The backend SDK lets you interact with the Nango API. It is available on NPM as @nangohq/node.

Instantiate the backend SDK

Install it with your favorite package manager, e.g.:

npm i -S @nangohq/node

Instantiate the Nango class:

import { Nango } from '@nangohq/node';

const nango = new Nango({ apiKey: '<API-KEY>' });

Parameters

Show properties
configobjectrequired
Show config
apiKeystringrequired

Your environment API key, found in Environment Settings > API Keys in the Tenklo UI. Sent as the bearer token for API calls. This key should never be shared.

secretKeystringdeprecated

Deprecated alias for apiKey, kept for backward compatibility. Provide either apiKey or secretKey.

webhookSigningKeystring

The environment's webhook signing key, found in Environment Settings > Webhooks > Signing key. Used by verifyIncomingWebhookRequest to validate incoming webhook signatures. Falls back to secretKey when omitted. On environments created after 2026-04-20 (or any environment that later rotated its API key), the signing key differs from the API key, so set this explicitly to verify webhooks.

hoststring

Omitting the host points to Tenklo Cloud. For local development, use http://localhost:3003. Use your instance URL if self-hosting.

Rate limits

The Tenklo SDK is rate-limited to prevent abuse and ensure fair usage across all clients. The rate limit is enforced on a per-account basis, with a fixed window of time and a maximum number of requests allowed within that window.

If a client exceeds the rate limit, the API will respond with a 429 Too Many Requests status code. In this case, the Retry-After header is included, indicating the number of seconds the client should wait before making another request to avoid being rate-limited.

To handle rate limiting gracefully, clients should monitor for the 429 status code and honor the Retry-After header value provided in the response.

// Example:
try {
    const res = await nango.listIntegrations();
    ...
} catch(err) {
    if (err.response.status === 429) {
        const retryAfter = err.response.headers['retry-after'];
        // wait and retry
        ...
    }
    ...
}

Integrations

List all integrations

Returns a list of integrations.

await nango.listIntegrations()

Example Response

Show properties
{
    "configs": [
        {
            "unique_key": "slack-nango-community",
            "provider": "slack",
            "logo": "http://localhost:3003/images/template-logos/slack.svg",
            "created_at": "2023-10-16T08:45:26.241Z",
            "updated_at": "2023-10-16T08:45:26.241Z",
        },
        {
            "unique_key": "github-prod",
            "provider": "github",
            "logo": "http://localhost:3003/images/template-logos/github.svg",
            "created_at": "2023-10-16T08:45:26.241Z",
            "updated_at": "2023-10-16T08:45:26.241Z",
        },
    ]
}

Get an integration

Returns a specific integration.

await nango.getIntegration({ uniqueKey: <UNIQUE_KEY> });

// Deprecated
await nango.getIntegration(<INTEGRATION-ID>);

Parameters

Show properties
uniqueKeystringrequired

The integration ID

includearray

Include sensitive data. Allowed values: webhook, credentials

Example Response

Show properties
{
    "data": {
        "unique_key": "slack-nango-community",
        "display_name": "Slack",
        "provider": "slack",
        "logo": "http://localhost:3003/images/template-logos/slack.svg",
        "forward_webhooks": true,
        "missing_fields": [],
        "created_at": "2023-10-16T08:45:26.241Z",
        "updated_at": "2023-10-16T08:45:26.241Z"
    }
}

missing_fields lists the credential fields the integration may need before connections can be created, for example oauth_client_id. It is empty once the integration is fully configured, when it uses a Nango-provided developer app, or when the auth mode collects everything at connection time.

Create an integration

Create a new integration.

await nango.createIntegration({ provider: '<PROVIDER-ID>', unique_key: '<UNIQUE_KEY>' });

Parameters

Show properties
providerstringrequired

The ID of the API provider in Tenklo (cf. providers.yaml for a list of API provider IDs.)

unique_keystringrequired

The integration ID.

display_namestring

The display name of this integration

credentialsRecord<string, string>

For OAUTH1, OAUTH2, TBA, APP, and CUSTOM credentials, you can create an integration without credentials or with partial credentials, then provide missing credentials with an update call or as per-connection overrides.

integration_configRecord<string, string>

Provider-specific configuration for providers with an integration_config schema (e.g. private-api-generic, aws-sigv4). See the provider's integration page for fields.

forward_webhooksboolean

Whether to forward webhooks received for this integration. Defaults to true.

Example Response

Show properties
{
    "data": {
        "unique_key": "slack-nango-community",
        "provider": "slack",
        "logo": "http://localhost:3003/images/template-logos/slack.svg",
        "created_at": "2023-10-16T08:45:26.241Z",
        "updated_at": "2023-10-16T08:45:26.241Z",
    }
}

Update an integration

Patch an integration. The first argument identifies the integration; all body fields are optional and only the fields you pass are updated.

await nango.updateIntegration({ uniqueKey: '<UNIQUE_KEY>' }, { display_name: 'New name' });

Parameters

Show properties
uniqueKeystringrequired

The integration ID to update. Passed in the first argument.

unique_keystring

A new integration ID, to rename the integration. Not allowed when the integration has active connections.

display_namestring

The display name of this integration

forward_webhooksboolean

Whether to forward webhooks received for this integration.

credentialsRecord<string, string>

The credentials to include depend on the specific integration that you want to update. Only the fields you pass are updated; the ones you leave out keep their current value.

integration_configRecord<string, string>

Provider-specific configuration for providers with an integration_config schema. Only the fields you pass are updated.

customRecord<string, string>

Free-form custom values for providers without an integration_config schema.

Example Response

Show properties
{
    "data": {
        "unique_key": "slack-nango-community",
        "provider": "slack",
        "logo": "http://localhost:3003/images/template-logos/slack.svg",
        "created_at": "2023-10-16T08:45:26.241Z",
        "updated_at": "2023-10-16T08:45:26.241Z",
    }
}

Delete an integration

Deletes a specific integration.

await nango.deleteIntegration(<UNIQUE_KEY>);

Parameters

Show properties
uniqueKeystringrequired

The integration ID.

Example Response

Show properties
{
    "success": true
}

Connect

Create a connect session

Create a connect session. The token is short-lived and lasts for 30 minutes.

const { data } = await nango.createConnectSession({
  // Recommended: copied onto the connection and included in auth webhooks.
  tags: {
    end_user_id: '<END-USER-ID>',
    end_user_email: '<END-USER-EMAIL>',
    organization_id: '<ORGANIZATION-ID>'
  },

  allowed_integrations: ['<INTEGRATION-ID-1>', '<INTEGRATION-ID-2>'],
  integrations_config_defaults: {
    <INTEGRATION-ID-1>: {
      connection_config: {
        <CONFIG-KEY>: '<VALUE>'
      }
    }
  }
});

Parameters

Show properties
end_userobjectdeprecated

Deprecated. Use tags instead.

organizationobjectdeprecated

Deprecated. Use tags instead.

Show organization
idstringrequired

The unique identifier for the organization.

display_namestring

The display name of the organization.

allowed_integrationsstring[]

An array of integration IDs that are allowed for this session.

tagsRecord<string, string>

Optional. Tags that will be copied onto the created connection and included in auth webhooks.

integrations_config_defaultsobject

Default configuration for specific integrations. For OAuth2 integrations, you can allow end-users to provide their own OAuth client credentials by setting empty oauth_client_id_override and oauth_client_secret_override values:

integrations_config_defaults: {
  google: {
    connection_config: {
      oauth_client_id_override: '',
      oauth_client_secret_override: ''
    }
  }
}

When set to empty strings, Connect UI will display optional input fields for users to enter their own OAuth app credentials.

webhook_url_overridestring

Override the environment's webhook URLs for the connection created with this session. When set, that connection's webhooks are sent only to this URL. See Override webhook URLs per connection.

webhook_url_override: 'https://<your-tunnel>.ngrok.app/webhooks-from-nango'
overridesobject

Per-integration overrides. The key is your integration's unique ID (e.g. jira, slack, github) and the value is an object with the settings to override. Currently supports overriding the documentation URL shown in info icons during the connect flow:

overrides: {
  '<INTEGRATION-ID>': {
    docs_connect: 'https://your-docs.com/how-to-connect'
  }
}

Available with the Growth add-on.

Returns

Show properties
{
    "data": {
        "token": "nango_connect_session_4603dbca8a588315ba69b5bfddde52e72d312dc2d2870bd5e45da6357333601c",
        "connect_link": "https://connect.tenklo.dev/?session_token=nango_connect_session_4603dbca8a588315ba69b5bfddde52e72d312dc2d2870bd5e45da6357333601c",
        "expires_at": "2024-09-27T19:49:51.449Z"
    }
}

Create a reconnect session

Create a reconnect session for a given connection_id. You can pass tags to update connection attribution during reconnection. Use this method when a user needs to input new credentials or to manually refresh token.

This method is only compatible with connection_id created with a session token.
const { data } = await nango.createReconnectSession({
  // Required
  connection_id: '<CONNECTION-ID>',
  integration_id: '<INTEGRATION-ID>',

  // Optional
  tags: {
    end_user_id: '<END-USER-ID>',
    end_user_email: '<END-USER-EMAIL>',
    organization_id: '<ORGANIZATION-ID>'
  },
  integrations_config_defaults: {
    <INTEGRATION-ID-1>: {
      connection_config: {
        <CONFIG-KEY>: '<VALUE>'
      }
    }
  }
});

Parameters

Show properties
connection_idstringrequired

The unique identifier for the connection.

integration_idstringrequired

The unique identifier for the integration.

tagsRecord<string, string>

Optional. Tags to apply to the connection during reconnection.

end_userobjectdeprecated

Deprecated. Use tags instead.

organizationobjectdeprecated

Deprecated. Use tags instead.

Show organization
idstringrequired

The unique identifier for the organization.

display_namestring

The display name of the organization.

integrations_config_defaultsobject

Default configuration for specific integrations.

Returns

Show properties
{
    "data": {
        "token": "nango_connect_session_4603dbca8a588315ba69b5bfddde52e72d312dc2d2870bd5e45da6357333601c",
        "connect_link": "https://connect.tenklo.dev/?session_token=nango_connect_session_4603dbca8a588315ba69b5bfddde52e72d312dc2d2870bd5e45da6357333601c",
        "expires_at": "2024-09-27T19:49:51.449Z"
    }
}

Connections

List connections

Returns a list of connections without credentials.

await nango.listConnections({
    connectionId: 'connection-id',
    integrationId: 'integration-456',
    limit: 100,
    tags: { "end_user_id": 'user-123' }
});

Parameters

Show properties
paramsobject
Show params
connectionIdstring

Optional. Will exactly match a given connectionId. Can return multiple connections with the same ID across integrations.

userIdstringdeprecated

Deprecated. Prefer filtering connections using tags instead (e.g. end_user_id).

integrationIdstring

Optional. Filter by integration ID.

tagsRecord<string, string>

Optional. Filter by tags associated with connections.

Example Response

Show properties
{
    "connections": [
        {
            "id": 1,
            "connection_id": "test-1",
            "provider": "slack",
            "provider_config_key": "slack-nango-community",
            "created": "2023-06-03T14:53:22.051Z",
            "metadata": null,
            "tags": {
                "end_user_id": "your-internal-id",
                "end_user_email": "[email protected]",
                "organization_id": "user-organization-id"
            },
            "errors": []
        },
        {
            "id": 2,
            "connection_id": "test-2",
            "provider": "slack",
            "provider_config_key": "slack-nango-community",
            "created": "2023-06-03T15:00:14.945Z",
            "metadata": {
                "bot_id": "some-uuid"
            },
            "tags": {
                "end_user_id": "your-internal-id",
                "end_user_email": "[email protected]",
                "organization_id": "user-organization-id"
            },
            "errors": [{ "type": "auth", "log_id": "VrnbtykXJFckCm3HP93t"}]
        }
    ]
}

Get a connection (with credentials)

Returns a specific connection with credentials.

await nango.getConnection(<INTEGRATION-ID>, <CONNECTION-ID>);

The response content depends on the API authentication type (OAuth 2, OAuth 1, API key, Basic auth, etc.).

If you do not want to deal with collecting & injecting credentials in requests for multiple authentication types, use the Proxy (step-by-step guide).

When you fetch the connection with this API endpoint, Tenklo will check if the access token has expired. If it has, it will refresh it.

We recommend not caching tokens for longer than 5 minutes to ensure they are fresh.

Parameters

Show properties
providerConfigKeystringrequired

The integration ID.

connectionIdstringrequired

The connection ID.

forceRefreshboolean

Defaults to false. If false, the token will only be refreshed if it expires within 15 minutes. If true, a token refresh attempt will happen on each request. This is only useful for testing and should not be done at high traffic.

refreshTokenboolean

Defaults to false. If false, the refresh token is not included in the response, otherwise it is. In production, it is not advised to return the refresh token, for security reasons, since only the access token is needed to sign requests.

refreshGithubAppJwtTokenboolean

Defaults to false. If true, this will refresh the JWT token for GitHub App / Github App OAuth connections

Example Response

Show properties
{
    "id": 18393,
    "created_at": "2023-03-08T09:43:03.725Z",
    "updated_at": "2023-03-08T09:43:03.725Z",
    "provider_config_key": "github",
    "connection_id": "1",
    "credentials": {
        "type": "OAUTH2",
        "access_token": "gho_tsXLG73f....",
        "refresh_token": "gho_fjofu84u9....",
        "expires_at": "2024-03-08T09:43:03.725Z",
        "raw": { // Raw token response from the OAuth provider: Contents vary!
            "access_token": "gho_tsXLG73f....",
            "refresh_token": "gho_fjofu84u9....",
            "token_type": "bearer",
            "scope": "public_repo,user"
        }
    },
    "connection_config": {
        "subdomain": "myshop",
        "realmId": "XXXXX",
        "instance_id": "YYYYYYY"
    },
    "metadata": {
        "myProperty": "yes",
        "filter": "closed=true"
    }
}

Patch a connection

Patch a connection.

await nango.patchConnection({
    connectionId: '<CONNECTION-ID>',
    provider_config_key: '<INTEGRATION-ID>'
  },
  { ...body });

Parameters

Show properties
bodyPatchPublicConnection['Body']required

The body of the connection (see API Reference).

Show body
tagsobject

Connection tags (key/value strings). Keys are normalized to lowercase.

Show tags
{key}string

Maximum string length: 255

webhook_url_overridestring

Override the environment's webhook URLs for this connection. When set, this connection's webhooks are sent only to this URL. Pass an empty string to clear the override.

end_userobjectdeprecated

Deprecated. Use tags instead

Show end_user
idstringdeprecated

Deprecated. Uniquely identifies the end user.

emailstringdeprecated

Deprecated. The email address of the end user.

display_namestringdeprecated

Deprecated. The display name of the end user.

tagsobjectdeprecated

Deprecated. Tags associated with the end user. Only accepts string values, up to 64 keys.

Example Response

Show properties
{
    "success": true
}

Get connection metadata

Returns a connection's metadata.

await nango.getMetadata('<INTEGRATION-ID>', 'CONNECTION-ID');

If you know the structure of the metadata, you can specify a type;

interface CustomMetadata {
    anyKey: Record<string, string>;
}
const myTypedMetadata = await nango.getMetadata<CustomMetadata>('<INTEGRATION-ID>', '<CONNECTION-ID>');

Parameters

Show properties
providerConfigKeystringrequired

The integration ID of the connection.

connectionIdstringrequired

The connection ID.

Example Response

Show properties
{
    "custom_key1": "custom_value1"
}

Set connection metadata

Set custom metadata for the connection or connections (overrides existing metadata).

await nango.setMetadata('<INTEGRATION-ID>', 'CONNECTION-ID', { 'CUSTOM_KEY1': 'CUSTOM_VALUE1' });

# set an array of connection ids
await nango.setMetadata('<INTEGRATION-ID>', ['CONNECTION-ID', 'CONNECTION-ID-TWO'], { 'CUSTOM_KEY1': 'CUSTOM_VALUE1' });

Parameters

Show properties
providerConfigKeystringrequired

The integration ID of the connection.

connectionIdstring | string[]required

The connection ID or connection IDs.

metadataRecord<string, any>required

The custom metadata to store in the connection.

Response

Show properties
{
    "connection_id": "<string | [string]>",
    "provider_config_key": "<string>",
    "metadata": {
        "CUSTOM_KEY1": "CUSTOM_VALUE1"
    }
}

Edit connection metadata

Edit custom metadata for the connection or connections. Only overrides specified properties, not the entire metadata.

await nango.updateMetadata('<INTEGRATION-ID>', 'CONNECTION-ID', { 'CUSTOM_KEY1': 'CUSTOM_VALUE1' });

# update an array of connection ids
await nango.updateMetadata('<INTEGRATION-ID>', ['CONNECTION-ID', 'CONNECTION-ID-TWO'], { 'CUSTOM_KEY1': 'CUSTOM_VALUE1' });

Parameters

Show properties
providerConfigKeystringrequired

The integration ID of the connection.

connectionIdstring | string[]required

The connection ID or connection IDs.

metadataRecord<string, any>required

The custom metadata to store in the connection.

Response

Show properties
{
    "connection_id": "<string | [string]>",
    "provider_config_key": "<string>",
    "metadata": {
        "CUSTOM_KEY1": "CUSTOM_VALUE1"
    }
}

Delete a connection

Deletes a specific connection.

await nango.deleteConnection('<INTEGRATION-ID>', 'CONNECTION-ID');

Parameters

Show properties
providerConfigKeystringrequired

The integration ID of the connection.

connectionIdstringrequired

The connection ID.

Response

Empty response.

Wait for connection

Waits for a connection to be created for a given end user and integration. This is useful in agentic flows where you need to wait for a user to complete the OAuth flow before proceeding.

const connection = await nango.waitForConnection('<INTEGRATION-ID>', '<END-USER-ID>');

This method polls for a connection every 2 seconds for up to 60 seconds (30 attempts). If no connection is found within this time, it will throw a timeout error.

Parameters

Show properties
integrationKeystringrequired

The integration ID (provider config key).

userIdstringrequired

The end user ID to wait for a connection for.

Example Response

Show properties
{
    "id": 1,
    "connection_id": "<CONNECTION-ID>",
    "provider": "slack",
    "provider_config_key": "slack-integration",
    "created": "2023-06-03T14:53:22.051Z",
    "metadata": null,
    "errors": [],
    "tags": {
        "end_user_id": "user-123",
        "end_user_email": "[email protected]",
        "organization_id": "org-456"
    }
}

Integration functions

List functions

Returns the functions available to an integration, with pagination.

await nango.listFunctions({ uniqueKey: 'github' });

Filter by function type, name, or page:

await nango.listFunctions({ uniqueKey: 'github' }, { type: 'action', search: 'issue', page: 0, limit: 20 });

Parameters

Show properties
uniqueKeystringrequired

The integration ID.

typestring

Filter by function type: sync, action, or on-event.

searchstring

Case-insensitive filter on the function name.

pagenumber

Page to return (starts at 0, default 0).

limitnumber

Results per page (1-100, default 20).

Catalog actions have id: null, last_deployed: null, enabled: true, and source: "tools-catalog". A deployed action with the same name takes precedence.

Example Response

Show properties
{
    "data": [
        {
            "type": "action",
            "name": "create-issue",
            "description": "Create a GitHub issue",
            "returns": ["GithubIssue"],
            "json_schema": null,
            "id": 42,
            "enabled": true,
            "last_deployed": "2024-03-28T14:00:00.000Z",
            "source": "repo"
        },
        {
            "type": "sync",
            "name": "issues",
            "returns": ["GithubIssue"],
            "json_schema": null,
            "runs": "every hour",
            "auto_start": true,
            "track_deletes": false,
            "id": 43,
            "enabled": true,
            "last_deployed": "2024-03-28T14:00:00.000Z",
            "source": "repo"
        },
        {
            "type": "action",
            "name": "get-repository",
            "description": "Get a GitHub repository",
            "returns": ["GithubRepository"],
            "json_schema": null,
            "id": null,
            "enabled": true,
            "last_deployed": null,
            "source": "tools-catalog"
        }
    ],
    "pagination": {
        "total": 3,
        "page": 0,
        "limit": 20
    }
}

Get a function

Retrieves a function available to an integration. This can be a deployed function or a Tenklo catalog action.

await nango.getFunction({ uniqueKey: 'github', name: 'create-issue' });

Pass type to disambiguate when a sync and an action share the same name:

await nango.getFunction({ uniqueKey: 'github', name: 'issues' }, { type: 'sync' });

Parameters

Show properties
uniqueKeystringrequired

The integration ID.

namestringrequired

The function name.

typestring

Disambiguates when functions share a name: sync, action, or on-event.

Example Response

Show properties
{
    "data": {
        "type": "action",
        "name": "create-issue",
        "description": "Create a GitHub issue",
        "returns": ["GithubIssue"],
        "json_schema": null,
        "id": 42,
        "enabled": true,
        "last_deployed": "2024-03-28T14:00:00.000Z",
        "source": "repo"
    }
}

Get function code

Retrieves the source code of a function. For a Tenklo catalog action, the response contains the current catalog source even though the action has not been deployed.

await nango.getFunctionCode({ uniqueKey: 'github', name: 'create-issue' }, { type: 'action' });

Parameters

Show properties
uniqueKeystringrequired

The integration ID.

namestringrequired

The function name.

typestring

Disambiguates when functions share a name: sync, action, or on-event.

Example Response

Show properties
{
    "type": "action",
    "code": "export default createAction({ /* ... */ });"
}

Delete a function

Deletes a deployed function of an integration. The type is required.

await nango.deleteFunction({ uniqueKey: 'github', name: 'create-issue' }, { type: 'action' });

Parameters

Show properties
uniqueKeystringrequired

The integration ID.

namestringrequired

The function name.

typestringrequired

The function type: sync or action.

Example Response

Show properties
{
    "data": {
        "success": true
    }
}

Get integration functions config

Return the configuration for all integration functions

const scriptsConfig = await nango.getScriptsConfig();

Example Response

Show properties
[
    {
        "providerConfigKey": "demo-github-integration",
        "syncs": [
            {
                "name": "github-issue-example",
                "type": "sync",
                "models": [
                    {
                        "name": "GithubIssue",
                        "fields": [
                            {
                                "name": "id",
                                "type": "integer"
                            },
                            {
                                "name": "owner",
                                "type": "string"
                            },
                            {
                                "name": "repo",
                                "type": "string"
                            },
                            {
                                "name": "issue_number",
                                "type": "number"
                            },
                            {
                                "name": "title",
                                "type": "string"
                            },
                            {
                                "name": "author",
                                "type": "string"
                            },
                            {
                                "name": "author_id",
                                "type": "string"
                            },
                            {
                                "name": "state",
                                "type": "string"
                            },
                            {
                                "name": "date_created",
                                "type": "date"
                            },
                            {
                                "name": "date_last_modified",
                                "type": "date"
                            },
                            {
                                "name": "body",
                                "type": "string"
                            }
                        ]
                    }
                ],
                "sync_type": "FULL", // DEPRECATED
                "runs": "every half hour",
                "track_deletes": false,
                "auto_start": false,
                "last_deployed": "2024-02-28T20:16:38.052Z",
                "is_public": false,
                "pre_built": false,
                "version": "4",
                "attributes": {},
                "input": {},
                "returns": [
                    "GithubIssue"
                ],
                "description": "Fetches the Github issues from all a user's repositories.\nDetails: doesn't track deletes, metadata is not required.\n",
                "scopes": [
                    "public_repo"
                ],
                "endpoints": [
                    {
                        "GET": "/github/issue-example"
                    }
                ],
                "nango_yaml_version": "v2",
                "webhookSubscriptions": []
            }
        ],
        "actions": [
            {
                "name": "fetch-issues",
                "type": "action",
                "models": [
                    {
                        "name": "GithubIssue",
                        "fields": [
                            {
                                "name": "id",
                                "type": "integer"
                            },
                            {
                                "name": "owner",
                                "type": "string"
                            },
                            {
                                "name": "repo",
                                "type": "string"
                            },
                            {
                                "name": "issue_number",
                                "type": "number"
                            },
                            {
                                "name": "title",
                                "type": "string"
                            },
                            {
                                "name": "author",
                                "type": "string"
                            },
                            {
                                "name": "author_id",
                                "type": "string"
                            },
                            {
                                "name": "state",
                                "type": "string"
                            },
                            {
                                "name": "date_created",
                                "type": "date"
                            },
                            {
                                "name": "date_last_modified",
                                "type": "date"
                            },
                            {
                                "name": "body",
                                "type": "string"
                            }
                        ]
                    }
                ],
                "runs": "",
                "is_public": false,
                "pre_built": false,
                "version": "4",
                "last_deployed": "2024-02-28T20:16:38.052Z",
                "attributes": {},
                "returns": [
                    "GithubIssue"
                ],
                "description": "",
                "scopes": [],
                "input": {},
                "endpoints": [
                    {
                        "GET": "/github/issues"
                    }
                ],
                "nango_yaml_version": "v2"
            }
        ],
        "postConnectionScripts": [],
        "provider": "github"
    }
]

You can also pass in an optional argument with the value of nango or openai. The default is nango

const scriptsConfig = await nango.getScriptsConfig('openai');
Show properties
{
  "data": [
    {
      "name": "calendars",
      "description": "Sync the user's calendar list.\nIncludes all calendars the user has access to.",
      "parameters": {
        "type": "object",
        "properties": {},
        "required": []
      }
    },
    {
      "name": "events",
      "description": "Sync calendar events from the primary calendar.\nIncludes events from the past month.",
      "parameters": {
        "type": "object",
        "properties": {},
        "required": []
      }
    },
    {
      "name": "cancel-event",
      "description": "Cancel/delete an event by searching for it.\n\nInput parameters:\n- title: The title of the event to cancel\n- date: The date of the event in ISO format\n- time: The time of the event in 24-hour format (HH:mm)\n- calendar: Calendar ID where to search for the event\n- id: Calendar identifier\n- summary: Calendar name/title\n- description: Calendar description\n- location: Calendar location\n- timeZone: Time zone of the calendar\n- backgroundColor: Calendar color in UI\n- foregroundColor: Text color in UI\n- selected: Whether the calendar is selected in UI\n- accessRole: User's access role for the calendar\n- primary: Whether this is the primary calendar",
      "parameters": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Calendar identifier"
          },
          "summary": {
            "type": "string",
            "description": "Calendar name/title"
          },
          "description": {
            "type": "string",
            "description": "Calendar description"
          },
          "location": {
            "type": "string",
            "description": "Calendar location"
          },
          "timeZone": {
            "type": "string",
            "description": "Time zone of the calendar"
          },
          "backgroundColor": {
            "type": "string",
            "description": "Calendar color in UI"
          },
          "foregroundColor": {
            "type": "string",
            "description": "Text color in UI"
          },
          "selected": {
            "type": "boolean",
            "description": "Whether the calendar is selected in UI"
          },
          "accessRole": {
            "type": "string",
            "description": "User's access role for the calendar"
          },
          "primary": {
            "type": "boolean",
            "description": "Whether this is the primary calendar"
          },
          "title": {
            "type": "string",
            "description": "The title of the event to cancel"
          },
          "date": {
            "type": "string",
            "description": "The date of the event in ISO format"
          },
          "time": {
            "type": "string",
            "description": "The time of the event in 24-hour format (HH:mm)"
          },
          "calendar": {
            "type": "string",
            "description": "Calendar ID where to search for the event"
          }
        },
        "required": [
          "id",
          "summary"
        ]
      }
    },
    {
      "name": "create-event",
      "description": "Create a new calendar event.\n\nInput parameters:\n- summary: The title/summary of the event\n- description: A detailed description of the event\n- location: The location of the event\n- start: The start time of the event in ISO format\n- end: The end time of the event in ISO format\n- attendees: List of email addresses to invite\n- recurrence: List of recurrence rules in RRULE format\n- calendar: Calendar ID to create the event in\n- id: Calendar identifier\n- timeZone: Time zone of the calendar\n- backgroundColor: Calendar color in UI\n- foregroundColor: Text color in UI\n- selected: Whether the calendar is selected in UI\n- accessRole: User's access role for the calendar\n- primary: Whether this is the primary calendar",
      "parameters": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Calendar identifier"
          },
          "summary": {
            "type": "string",
            "description": "The title/summary of the event"
          },
          "description": {
            "type": "string",
            "description": "A detailed description of the event"
          },
          "location": {
            "type": "string",
            "description": "The location of the event"
          },
          "timeZone": {
            "type": "string",
            "description": "Time zone of the calendar"
          },
          "backgroundColor": {
            "type": "string",
            "description": "Calendar color in UI"
          },
          "foregroundColor": {
            "type": "string",
            "description": "Text color in UI"
          },
          "selected": {
            "type": "boolean",
            "description": "Whether the calendar is selected in UI"
          },
          "accessRole": {
            "type": "string",
            "description": "User's access role for the calendar"
          },
          "primary": {
            "type": "boolean",
            "description": "Whether this is the primary calendar"
          },
          "start": {
            "type": "string",
            "description": "The start time of the event in ISO format"
          },
          "end": {
            "type": "string",
            "description": "The end time of the event in ISO format"
          },
          "attendees": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "List of email addresses to invite"
          },
          "recurrence": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "List of recurrence rules in RRULE format"
          },
          "calendar": {
            "type": "string",
            "description": "Calendar ID to create the event in"
          }
        },
        "required": [
          "id",
          "summary",
          "start",
          "end"
        ]
      }
    },
    {
      "name": "move-event",
      "description": "Move an event to a different time or calendar.\n\nInput parameters:\n- eventId: The ID of the event to move\n- title: The title of the event to move\n- sourceStart: The current start time of the event\n- start: The new start time for the event in ISO format\n- end: The new end time for the event in ISO format\n- calendar: Calendar ID to move the event to\n- id: Calendar identifier\n- summary: Calendar name/title\n- description: Calendar description\n- location: Calendar location\n- timeZone: Time zone of the calendar\n- backgroundColor: Calendar color in UI\n- foregroundColor: Text color in UI\n- selected: Whether the calendar is selected in UI\n- accessRole: User's access role for the calendar\n- primary: Whether this is the primary calendar",
      "parameters": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Calendar identifier"
          },
          "summary": {
            "type": "string",
            "description": "Calendar name/title"
          },
          "description": {
            "type": "string",
            "description": "Calendar description"
          },
          "location": {
            "type": "string",
            "description": "Calendar location"
          },
          "timeZone": {
            "type": "string",
            "description": "Time zone of the calendar"
          },
          "backgroundColor": {
            "type": "string",
            "description": "Calendar color in UI"
          },
          "foregroundColor": {
            "type": "string",
            "description": "Text color in UI"
          },
          "selected": {
            "type": "boolean",
            "description": "Whether the calendar is selected in UI"
          },
          "accessRole": {
            "type": "string",
            "description": "User's access role for the calendar"
          },
          "primary": {
            "type": "boolean",
            "description": "Whether this is the primary calendar"
          },
          "eventId": {
            "type": "string",
            "description": "The ID of the event to move"
          },
          "title": {
            "type": "string",
            "description": "The title of the event to move"
          },
          "sourceStart": {
            "type": "string",
            "description": "The current start time of the event"
          },
          "start": {
            "type": "string",
            "description": "The new start time for the event in ISO format"
          },
          "end": {
            "type": "string",
            "description": "The new end time for the event in ISO format"
          },
          "calendar": {
            "type": "string",
            "description": "Calendar ID to move the event to"
          }
        },
        "required": [
          "id",
          "summary",
          "start",
          "end"
        ]
      }
    },
    {
      "name": "whoami",
      "description": "Get information about the authenticated user.\n\nNo input parameters.",
      "parameters": {
        "type": "object",
        "properties": {},
        "required": []
      }
    },
    {
      "name": "invite-user-to-repository",
      "description": "Invite a user to a GitHub repository.\n\nInput parameters:\n- owner: The owner (user or organization) of the repository\n- repo: The name of the repository to invite the user to\n- username: The GitHub username of the user to invite\n- permission: The permission level to grant (\"pull\", \"push\", \"admin\", \"maintain\", \"triage\")",
      "parameters": {
        "type": "object",
        "properties": {
          "owner": {
            "type": "string",
            "description": "The owner (user or organization) of the repository"
          },
          "repo": {
            "type": "string",
            "description": "The name of the repository to invite the user to"
          },
          "username": {
            "type": "string",
            "description": "The GitHub username of the user to invite"
          },
          "permission": {
            "type": "string",
            "description": "The permission level to grant (\"pull\", \"push\", \"admin\", \"maintain\", \"triage\")"
          }
        },
        "required": [
          "owner",
          "repo",
          "username"
        ]
      }
    }
  ]
}

Syncs

Get records

Returns the synced data.

import type { ModelName } from '<path-to-nango-integrations>/models'

const records = await nango.listRecords<ModelName>({
    providerConfigKey: '<INTEGRATION-ID>',
    connectionId: '<CONNECTION-ID>',
    model: '<MODEL-NAME>'
});

Parameters

Show properties
configobjectrequired
Show config
providerConfigKeystringrequired

The integration ID.

connectionIdstringrequired

The connection ID.

modelstringrequired

The name of the model of the data you want to retrieve.

variantstring

The variant of the model to fetch. When omitted, the default base variant is used.

cursorstring

Each record from this endpoint comes with a synchronization cursor in _nango_metadata.cursor.

Save the last fetched record's cursor to track how far you've synced.

By providing the cursor to this method, you'll continue syncing from where you left off, receiving only post-cursor changes.

This same cursor is used to paginate through the results of this endpoint.

limitnumber

The maximum number of records to return. Defaults to 100.

filterstring

Filter to only show results that have been added or updated or deleted.

Available options: added, updated, deleted

modifiedAfterstring

Timestamp, e.g. 2023-05-31T11:46:13.390Z. If passed, only records modified after this timestamp are returned, otherwise all records are returned.

idsstring[]

An array of string containing a list of your records IDs. The list will be filtered to include only the records with a matching ID.

deltastringdeprecated

Deprecated. (use modifiedAfter) Timestamp, e.g. 2023-05-31T11:46:13.390Z. If passed, only records modified after this timestamp are returned, otherwise all records are returned.

Example Response

This endpoint returns a list of records, ordered by modification date ascending. If some records are updated while you paginate through this endpoint, you might see these records multiple times.

Show properties
{
    records:
        [
            {
                id: 123,
                ..., // Fields as specified in the model you queried
                _nango_metadata: {
                    deleted_at: null,
                    last_action: 'ADDED',
                    first_seen_at: '2023-09-18T15:20:35.941305+00:00',
                    last_modified_at: '2023-09-18T15:20:35.941305+00:00',
                    cursor: 'MjAyNC0wMi0yNlQwMzowMDozOS42MjMzODgtMDU6MDB8fGVlMDYwM2E1LTEwNDktNDA4Zi05YTEwLTJjNzVmNDkwODNjYQ=='
                }
            },
            ...
        ],
    next_cursor: "Y3JlYXRlZF9hdF4yMDIzLTExLTE3VDExOjQ3OjE0LjQ0NyswMjowMHxpZF4xYTE2MTYwMS0yMzk5LTQ4MzYtYWFiMi1mNjk1ZWI2YTZhYzI"
}

Prune records

Prunes record payloads from Tenklo's cache for a given model and connection up to a specified cursor. The payload is emptied but record metadata is retained. Payload can be restored by re-syncing the data.

const result = await nango.pruneRecords({
    providerConfigKey: '<INTEGRATION-ID>',
    connectionId: '<CONNECTION-ID>',
    model: '<MODEL-NAME>',
    untilCursor: '<CURSOR>'
});

console.log(`Pruned ${result.count} records. More records available: ${result.has_more}`);

Parameters

Show properties
configobjectrequired
Show config
providerConfigKeystringrequired

The integration ID.

connectionIdstringrequired

The connection ID.

modelstringrequired

The name of the model from which to prune records.

variantstring

The variant of the model. When omitted, the default base variant is used.

untilCursorstringrequired

The cursor up to which records should be pruned. Only records with a cursor less than or equal to this value will be pruned.

limitnumber

The maximum number of records to prune in this operation.

Response

Show properties
{
    "count": 150,
    "has_more": true
}
countnumber

The number of records that were pruned in this operation.

has_moreboolean

Indicates whether there are more records available for pruning that match the criteria. If true, you must call the method again to continue pruning records.

Trigger sync(s)

Triggers an additional, one-off execution of specified sync(s) for a given connection or all applicable connections if no connection is specified.

// Simple usage - trigger sync preserving checkpoint
await nango.triggerSync('<INTEGRATION-ID>', ['SYNC_NAME1', 'SYNC_NAME2'], '<CONNECTION_ID>');

// Reset checkpoint and start fresh
await nango.triggerSync('<INTEGRATION-ID>', ['SYNC_NAME1'], '<CONNECTION_ID>', { reset: true });

// Reset checkpoint and clear all cached records
await nango.triggerSync('<INTEGRATION-ID>', ['SYNC_NAME1'], '<CONNECTION_ID>', { reset: true, emptyCache: true });

// Using variants
await nango.triggerSync('<INTEGRATION-ID>', [
  { name: 'SYNC_NAME1', variant: 'VARIANT_A' },
  'SYNC_NAME2' // Uses default base variant
], '<CONNECTION_ID>', { reset: true });

Parameters

Show properties
providerConfigKeystringrequired

The integration ID.

syncsArray<string | {name: string, variant: string}>required

The name of the syncs to trigger. If the array is empty, all syncs are triggered. Each sync can be specified as either:

  • A string: the sync name (uses the default base variant)
  • An object: with name and variant properties to target a specific sync variant
connectionIdstring

The connection ID. If omitted, the sync will trigger for all relevant connections.

optsobject

Options for triggering the sync. See the checkpoints guide for details on reset and emptyCache.

Show properties
resetboolean

If true, clears the checkpoint before triggering the sync, causing it to start fresh.

emptyCacheboolean

If true, deletes all cached records associated with the sync before triggering. Must be used with reset: true.

Response

Empty response.

Start schedule for sync(s)

Starts the schedule of specified sync(s) for a given connection or all applicable connections if no connection is specified. Upon starting the schedule, the sync will execute immediately and then continue to run at the specified frequency. If the schedule was already started, this will have no effect.

// Simple usage with sync names
await nango.startSync('<INTEGRATION-ID>', ['SYNC_NAME1', 'SYNC_NAME2'], '<CONNECTION_ID>');

// Using variants
await nango.startSync('<INTEGRATION-ID>', [
  { name: 'SYNC_NAME1', variant: 'VARIANT_A' },
  'SYNC_NAME2' // Uses default base variant
], '<CONNECTION_ID>');

Parameters

Show properties
providerConfigKeystringrequired

The integration ID.

syncsArray<string | {name: string, variant: string}>required

The name of the syncs that should be triggered. Each sync can be specified as either:

  • A string: the sync name (uses the default base variant)
  • An object: with name and variant properties to target a specific sync variant
connectionIdstring

The connection ID. If omitted, the sync will trigger for all relevant connections.

Response

Empty response.

Pause schedule for sync(s)

Pauses the schedule of specified sync(s) for a given connection or all applicable connections if no connection is specified.

// Simple usage with sync names
await nango.pauseSync('<INTEGRATION-ID>', ['SYNC_NAME1', 'SYNC_NAME2'], '<CONNECTION_ID>');

// Using variants
await nango.pauseSync('<INTEGRATION-ID>', [
  { name: 'SYNC_NAME1', variant: 'VARIANT_A' },
  'SYNC_NAME2' // Uses default base variant
], '<CONNECTION_ID>');

Parameters

Show properties
providerConfigKeystringrequired

The integration ID.

syncsArray<string | {name: string, variant: string}>required

The name of the syncs that should be paused. Each sync can be specified as either:

  • A string: the sync name (uses the default base variant)
  • An object: with name and variant properties to target a specific sync variant
connectionIdstring

The connection ID. If omitted, the sync will pause for all relevant connections.

Response

Empty response.

Sync status

Get the status of specified sync(s) for a given connection or all applicable connections if no connection is specified.

// Simple usage with sync names
await nango.syncStatus('<INTEGRATION-ID>', ['SYNC_NAME1', 'SYNC_NAME2'], '<CONNECTION_ID>');

// Using variants
await nango.syncStatus('<INTEGRATION-ID>', [
  { name: 'SYNC_NAME1', variant: 'VARIANT_A' },
  'SYNC_NAME2' // Uses default base variant
], '<CONNECTION_ID>');

// Get all syncs
await nango.syncStatus('<INTEGRATION-ID>', '*', '<CONNECTION_ID>');

Parameters

Show properties
providerConfigKeystringrequired

The integration ID.

syncsstring | Array<string | {name: string, variant: string}>required

Either "*" to return all syncs, or an array of syncs to fetch status for. When using an array, each sync can be specified as either:

  • A string: the sync name (uses the default base variant)
  • An object: with name and variant properties to target a specific sync variant
connectionIdstring

The connection ID. If omitted, all connections will be surfaced.

Response

Show properties
{
    "syncs": [
        {
            "id": "<string>",
            "connection_id": "<string>",
            "name": "<string>",
            "variant": "<string>",
            "status": "RUNNING",
            "type": "INCREMENTAL", // DEPRECATED
            "finishedAt": "<string>",
            "nextScheduledSyncAt": "<string>",
            "frequency": "<string>",
            "latestResult": {
                "<string>": {
                    "added": <number>,
                    "updated": <number>,
                    "deleted": <number>,
                }
            },
            "recordCount": {
                "<string>": <number>
                ...
            },
            "checkpoint": {
                "<string>": "<string | number | boolean>"
                ...
            }
        }
    ]
}

The checkpoint field contains the last saved checkpoint for the sync. Learn more about checkpoints.

Override sync connection frequency

Override a sync's default frequency for a specific connection, or revert to the default frequency.

// For base variant
await nango.updateSyncConnectionFrequency('<INTEGRATION-ID>', 'SYNC_NAME', '<CONNECTION_ID>', '<FREQUENCY>');

// For a specific variant
await nango.updateSyncConnectionFrequency('<INTEGRATION-ID>', { name: 'SYNC_NAME', variant: 'VARIANT_NAME' }, '<CONNECTION_ID>', '<FREQUENCY>');

Parameters

Show properties
providerConfigKeystringrequired

The integration ID.

syncstring | {name: string, variant: string}required

The sync to update. Can be specified as either:

  • A string: the sync name (uses the default base variant)
  • An object: with name and variant properties to target a specific sync variant
connectionIdstringrequired

The connection ID.

frequencystringrequired

The frequency you want to set (ex: 'every hour'). Set to null to revert to the default frequency. Uses the https://github.com/vercel/ms notations. See minimum sync frequency for details.

Response

Show properties
{
    "frequency": "<string>"
}

Create sync variant

Creates a new sync variant for a specific connection. Sync variants allow you to run multiple instances of the same sync with different configurations.

await nango.createSyncVariant({
  provider_config_key: '<INTEGRATION-ID>',
  connection_id: '<CONNECTION_ID>',
  name: 'SYNC_NAME',
  variant: 'VARIANT_NAME'
});

Parameters

Show properties
propsobjectrequired
Show props
provider_config_keystringrequired

The integration ID.

connection_idstringrequired

The connection ID.

namestringrequired

The name of the sync.

variantstringrequired

The name of the variant to create. Cannot be "base" (protected name).

Example Response

Show properties
{
    "id": "12345",
    "name": "SYNC_NAME",
    "variant": "VARIANT_NAME"
}

Delete sync variant

Deletes a sync variant for a specific connection.

await nango.deleteSyncVariant({
  provider_config_key: '<INTEGRATION-ID>',
  connection_id: '<CONNECTION_ID>',
  name: 'SYNC_NAME',
  variant: 'VARIANT_NAME'
});

Parameters

Show properties
propsobjectrequired
Show props
provider_config_keystringrequired

The integration ID.

connection_idstringrequired

The connection ID.

namestringrequired

The name of the sync.

variantstringrequired

The name of the variant to delete. Cannot be "base" (protected name).

Example Response

Show properties
{
    "success": true
}

Get environment variables

Retrieve the environment variables as added in the Tenklo dashboard.

await nango.getEnvironmentVariables();

Parameters

No parameters.

Response

Show properties
[
    {
        "name": "MY_SECRET_KEY",
        "value": "SK_373892NSHFNCOWFO..."
    }
]

Actions

Trigger an action

Triggers an action for a connection.

await nango.triggerAction('<INTEGRATION-ID>', '<CONNECTION_ID>', '<ACTION-NAME>', { 'custom_key1': 'custom_value1' });

Parameters

Show properties
providerConfigKeystringrequired

The integration ID.

connectionIdstringrequired

The connection ID.

actionNamestringrequired

The name of the action to trigger.

inputunknown

The necessary input for your action's exec function.

Response

Show properties
{
    "your-properties": "The data returned by the action"
}

The output of an action cannot exceed 10MB.

Trigger an action asynchronously

Triggers an action asynchronously for a connection. This allows you to start an action and retrieve the results later.

const { id, statusUrl } = await nango.triggerActionAsync('<INTEGRATION-ID>', '<CONNECTION_ID>', '<ACTION-NAME>', { 'custom_key1': 'custom_value1' });

Parameters

Show properties
providerConfigKeystringrequired

The integration ID.

connectionIdstringrequired

The connection ID.

actionNamestringrequired

The name of the action to trigger.

inputunknown

The necessary input for your action's exec function.

Response

Show properties
{
    "id": "action_123456",
    "statusUrl": "/action/action_123456"
}

Get async action result

Retrieves the result of an asynchronous action.

// Using the id
const result = await nango.getAsyncActionResult({ id: 'action_123456' });

// OR using the statusUrl
const result = await nango.getAsyncActionResult({ statusUrl: '/action/action_123456' });

Parameters

Show properties
propsobjectrequired
Show props
idstring

The ID of the action to retrieve the result for.

statusUrlstring

The URL where the result can be retrieved.

Response

Show properties
{
    "your-properties": "The data returned by the action"
}

Proxy

Makes an HTTP request using the proxy:

const config = {
    endpoint: '/some-endpoint',
    providerConfigKey: '<INTEGRATION-ID>',
    connectionId: '<CONNECTION-ID>'
};

await nango.get(config); // GET request
await nango.post(config); // POST request
await nango.put(config); // PUT request
await nango.patch(config); // PATCH request
await nango.delete(config); // DELETE request

Parameters

Show properties
configobjectrequired
Show config
endpointstringrequired

The endpoint of the request.

providerConfigKeystringrequired

The integration ID (for credential injection).

connectionIdstringrequired

The connection ID (for credential injection).

headersRecord<string, string>

The headers of the request.

paramsRecord<string, string | number>

The query parameters of the request.

dataunknown

The body of the request.

retriesnumber

The number of retries in case of failure (with exponential back-off). Optional, default 0.

retryOnnumber[]

Array of additional status codes to retry a request in addition to the 5xx, 429, ECONNRESET, ETIMEDOUT, and ECONNABORTED

baseUrlOverridestring

The API base URL. Can be omitted if the base URL is configured for this API in the providers.yaml.

decompressboolean

Override the decompress option when making requests. Optional, defaults to false

responseType'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'

The type of the response.

Response

Show properties

The response from the external API is passed back to you exactly as Tenklo gets it:

  • response code
  • response headers
  • response body

Providers

List all providers

Returns a list of providers.

await nango.listProviders()

Example Response

Show properties
{
    "data": [
        {
            "name": "posthog",
            "categories": ["dev-tools"],
            "auth_mode": "API_KEY",
            "proxy": {
                "base_url": "https://api.posthog.com",
            },
            "docs": "https://docs.tenklo.dev/api-integrations/posthog"
        }
    ]
}

Get a provider

Returns a specific provider.

await nango.getProvider({ provider: <NAME> })

Example Response

Show properties
{
    "data": {
        "name": "posthog",
        "categories": ["dev-tools"],
        "auth_mode": "API_KEY",
        "proxy": {
            "base_url": "https://api.posthog.com",
        },
        "docs": "https://docs.tenklo.dev/api-integrations/posthog"
    }
}

Get provider templates

Returns the function templates available for a provider.

await nango.getProviderTemplates({ provider: <NAME> })

Parameters

Show properties
providerstringrequired

The provider name.

Example Response

Show properties
{
    "data": [
        {
            "type": "action",
            "name": "create-issue",
            "description": "Create a GitHub issue",
            "returns": ["GithubIssue"],
            "json_schema": null
        }
    ]
}

Webhooks from Tenklo

Verify Webhook Signature

Asserts that a Webhook is coming from Tenklo's backend. Verification uses webhookSigningKey when the client is constructed with one, otherwise it falls back to secretKey. On environments created after 2026-04-20 (or any environment that later rotated its API key), the signing key differs from the API key, so construct the client with webhookSigningKey (from Environment Settings > Webhooks > Signing key).

const nango = new Nango({
    apiKey: '<API-KEY>',
    webhookSigningKey: '<WEBHOOK-SIGNING-KEY>'
});

async (req, res) => {
    const isValid = nango.verifyIncomingWebhookRequest(req.body, req.headers);
}

Questions, problems, feedback? Please reach out in the Slack community.

On this page