Connections API
Manage end-user connections — hosted connect URLs, direct credentials, OAuth flows, listing active connections, and disconnecting users from plugins.
Initiate Connection (Hosted Connect URL)
POST /api/v1/connections/initiateThe primary way to connect end-users. Returns a URL you can redirect or link the user to. NIN handles the rest — showing the right UI, collecting credentials or running OAuth, encrypting, and storing.
Only approved plugins can be connected. Attempting to initiate a connection to a plugin that is still in draft, pending_review, declined, or revision_requested status will return a 404 error.
Authentication
API key required: Authorization: Bearer nk_live_...
Request Body
{
"userId": "user_123",
"plugin": "gmail",
"redirectUrl": "https://your-app.com/callback"
}| Field | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | Your app's end-user identifier |
plugin | string | Yes | Plugin slug (e.g. gmail, stripe, openai) |
authMethod | string | No | Specific auth method type to use (see behavior below) |
connectionType | string | No | "rest" (default) or "mcp". When "mcp", initiates an OAuth flow with PKCE targeting the plugin's MCP server. |
redirectUrl | string | Yes* | Where to send the user after connecting |
returnUrl, return_url, callbackUrl, and callback_url are accepted as aliases. If none are provided, NIN tries X-NIN-Redirect-URL, X-Return-URL, Referer, then Origin. Server-to-server callers should still send redirectUrl explicitly.
Response
{
"connectUrl": "https://connect.nin.in/connect/gmail?developerId=...&userId=...&redirectUrl=...",
"redirectUrl": "https://your-app.com/callback"
}Behavior
What connectUrl returns depends on the plugin's auth methods, connectionType, and whether you pass authMethod:
connectionType | authMethod param | What happens |
|---|---|---|
"mcp" | — | Returns an OAuth authorization URL with PKCE for the plugin's MCP server. Requires the plugin to have mcpConfig set. |
"rest" or omitted | "oauth2" | Returns the REST OAuth provider's authorization URL directly |
"rest" or omitted | Omitted (default is oauth2) | Same — goes straight to OAuth |
"rest" or omitted | Omitted (default is non-OAuth) | Returns a hosted connect page URL showing all available auth methods |
"rest" or omitted | "api_key" or other | Returns a hosted connect page URL filtered to that specific method |
Hosted Connect Page
When the connectUrl points to NIN's hosted connect page (/connect/[plugin]), the end-user sees:
- The plugin name and description
- All available auth methods (if
authMethodwas omitted) or a single method (if specified) - For MCP-enabled plugins: a "Connect with [Service] MCP" button that initiates OAuth with PKCE
- For OAuth methods: a button that redirects through the provider's authorization flow
- For API key / bearer token: a form to paste credentials
- For service accounts: a textarea for JSON credentials
- For no_auth: a simple "Connect" button
After the user completes the flow, they're redirected back to your redirectUrl with ?status=success or ?status=error&error=....
Example: MCP Connection
const session = nin.session("user_123");
const { connectUrl } = await session.initiateConnection({
plugin: "notion",
connectionType: "mcp",
redirectUrl: "https://your-app.com/callback",
});
// User clicks → Notion OAuth with PKCE → tokens stored → redirected backExample: "Connect your Gmail" Button
// in your backend
const session = nin.session("user_123");
const { connectUrl } = await session.initiateConnection({
plugin: "gmail",
redirectUrl: "https://your-app.com/settings?tab=integrations",
});
// send connectUrl to your frontend, render as a link or button
// the user clicks it → authorizes with Google → returns to your appExample: Show All Auth Options
If a plugin has multiple auth methods (e.g. Gmail has OAuth + service account), omit authMethod to let the user choose:
const { connectUrl } = await session.initiateConnection({
plugin: "gmail",
redirectUrl: "https://your-app.com/callback",
// no authMethod → hosted page shows all options
});Example: Force a Specific Method
const { connectUrl } = await session.initiateConnection({
plugin: "openai",
authMethod: "api_key",
redirectUrl: "https://your-app.com/callback",
});
// hosted page only shows the API key formCreate Connection (Direct)
POST /api/v1/connectionsSubmit credentials directly from your backend — no redirect or UI needed. Best for cases where you already have the user's credentials (e.g. they entered an API key in your own settings page).
Authentication
API key required: Authorization: Bearer nk_live_...
Request Body
{
"userId": "user_123",
"plugin": "openai",
"authMethod": "api_key",
"credentials": {
"api_key": "sk-..."
},
"accountLabel": "My OpenAI Key"
}| Field | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | Your app's end-user identifier |
plugin | string | Yes | Plugin slug |
authMethod | string | Yes | Auth method type (e.g. api_key, bearer_token) |
credentials | object | Yes | Key-value pairs of credentials |
accountLabel | string | No | Display label for this connection |
Response
{
"connected": true
}If a connection already exists for the same developer + user + plugin, the credentials are updated (upsert behavior).
Disconnect Connection
DELETE /api/v1/connectionsRemoves an end-user connection and clears any live MCP client cached for that user/plugin.
Authentication
API key required: Authorization: Bearer nk_live_...
Request Body
You can disconnect by plugin slug:
{
"userId": "user_123",
"plugin": "gmail"
}Or by a connection ID returned from GET /api/v1/connections:
{
"userId": "user_123",
"connectionId": "550e8400-e29b-41d4-a716-446655440002"
}| Field | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | Your app's end-user identifier |
plugin | string | Yes, unless connectionId is provided | Plugin slug to disconnect |
connectionId | string | Yes, unless plugin is provided | Specific connection ID returned by GET /api/v1/connections |
Response
{
"disconnected": true
}If no matching connection exists for the authenticated developer and userId, the API returns 404.
The SDK exposes this as:
await session.disconnectConnection({ plugin: "gmail" });
await session.disconnectConnection({
connectionId: "550e8400-e29b-41d4-a716-446655440002",
});List Connections
GET /api/v1/connections?userId=user_123Returns the user's active connections.
Authentication
API key required: Authorization: Bearer nk_live_...
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | The end-user identifier |
Response
{
"connections": [
{
"id": "550e8400-e29b-41d4-a716-446655440002",
"pluginSlug": "gmail",
"pluginName": "Gmail",
"pluginDescription": "Send and manage emails via Gmail API",
"pluginIconUrl": "https://example.com/gmail-icon.png",
"authMethodType": "oauth2",
"accountLabel": "work@company.com",
"hasMcpTokens": false,
"createdAt": "2025-01-15T10:30:00.000Z"
},
{
"id": "660e8400-e29b-41d4-a716-446655440003",
"pluginSlug": "notion",
"pluginName": "Notion",
"pluginDescription": "Search and update Notion pages",
"pluginIconUrl": "https://example.com/notion-icon.png",
"authMethodType": null,
"accountLabel": null,
"hasMcpTokens": true,
"createdAt": "2025-03-01T14:00:00.000Z"
}
]
}OAuth Callback
GET /api/v1/oauth/callbackThis is the fixed callback URL for all OAuth providers. You do not call this endpoint directly — it is called by the OAuth provider after user authorization.
The callback:
- Verifies the HMAC-signed state token
- Detects whether this is a REST or MCP OAuth flow (via
isMcpflag in state) - Finds the pending connection record
- Exchanges the authorization code for tokens (includes
code_verifierfor MCP PKCE flows) - Encrypts and stores the tokens — REST tokens in
credentials, MCP tokens inmcpAccessToken/mcpRefreshToken - Cleans up the pending connection
- Redirects the user to your
redirectUrlwith?status=successor?status=error&error=...