Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/api-catalog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> Compact provider slug catalog for agents. Use the slug to reconstruct provider-specific docs URLs only when needed.

Provider count: 874
Provider count: 875

URL patterns:
- Main provider page: https://nango.dev/docs/integrations/all/{slug}.md or https://nango.dev/docs/api-integrations/{slug}.md
Expand Down Expand Up @@ -658,6 +658,7 @@ URL patterns:
| `sage-intacct` | Sage Intacct | TWO_STEP | [docs](https://nango.dev/docs/integrations/all/sage-intacct.md) | [connect](https://nango.dev/docs/integrations/all/sage-intacct/connect.md) | | accounting, erp |
| `sage-intacct-cc` | Sage Intacct (Client Credentials) | TWO_STEP | [docs](https://nango.dev/docs/api-integrations/sage-intacct-cc.md) | [connect](https://nango.dev/docs/api-integrations/sage-intacct-cc/connect.md) | [setup](https://nango.dev/docs/api-integrations/sage-intacct-cc/connect.md) | accounting, erp |
| `sage-intacct-oauth` | Sage Intacct (OAuth) | OAUTH2 | [docs](https://nango.dev/docs/integrations/all/sage-intacct-oauth.md) | | | accounting, erp, popular |
| `sage-member` | SAGE Member | API_KEY | [docs](https://nango.dev/docs/api-integrations/sage-member.md) | [connect](https://nango.dev/docs/api-integrations/sage-member/connect.md) | | marketing, e-commerce |
| `sage-people` | Sage People | OAUTH2 | [docs](https://nango.dev/docs/integrations/all/sage-people.md) | [connect](https://nango.dev/docs/integrations/all/sage-people/connect.md) | | hr |
| `salesforce` | Salesforce | OAUTH2 | [docs](https://nango.dev/docs/api-integrations/salesforce.md) | [connect](https://nango.dev/docs/api-integrations/salesforce/connect.md) | [setup](https://nango.dev/docs/api-integrations/salesforce/salesforce-api-oauth-app-setup.md) | crm, popular |
| `salesforce-cc` | Salesforce (Client Credentials) | OAUTH2_CC | [docs](https://nango.dev/docs/api-integrations/salesforce-cc.md) | [connect](https://nango.dev/docs/api-integrations/salesforce-cc/connect.md) | | crm |
Expand Down
87 changes: 87 additions & 0 deletions docs/api-integrations/sage-member.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
title: 'SAGE Member'
sidebarTitle: 'SAGE Member'
description: 'Integrate your application with the SAGE Member API'
---

## Quickstart

Connect to SAGE Member with Nango and see data flow in 2 minutes.

<Steps>
<Step id="create-integration" title="Create the integration">
In Nango ([free signup](https://app.nango.dev)), go to [Integrations](https://app.nango.dev/dev/integrations) -> _Configure New Integration_ -> _SAGE Member_.
</Step>
<Step id="authorize-sage-member" title="Authorize SAGE Member">
Go to [Connections](https://app.nango.dev/dev/connections) -> _Add Test Connection_ -> _Authorize_, then enter your Account Number, Login ID, and Authentication Key. Later, you'll let your users do the same directly from your app.
</Step>
<Step id="call-the-api" title="Call the SAGE Member API">
Let's make your first request to the SAGE Member API. Replace the placeholders below with your [secret key](https://app.nango.dev/dev/environment-settings), [integration ID](https://app.nango.dev/dev/integrations), and [connection ID](https://app.nango.dev/dev/connections):
<Tabs>
<Tab title="cURL">

```bash
curl -X POST "https://api.nango.dev/proxy/ConnectAPI" \
-H "Authorization: Bearer <NANGO-SECRET-KEY>" \
-H "Provider-Config-Key: <INTEGRATION-ID>" \
-H "Connection-Id: <CONNECTION-ID>" \
-H "Content-Type: application/json" \
-d '{"serviceId": 101, "apiVer": 130}'
```

</Tab>

<Tab title="Node">

Install Nango's backend SDK with `npm i @nangohq/node`. Then run:

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

const nango = new Nango({ secretKey: '<NANGO-SECRET-KEY>' });

const res = await nango.post({
endpoint: '/ConnectAPI',
providerConfigKey: '<INTEGRATION-ID>',
connectionId: '<CONNECTION-ID>',
data: {
serviceId: 101,
apiVer: 130
}
});

console.log(res.data);
```
</Tab>

</Tabs>
Nango automatically injects your Account Number, Login ID, and Authentication Key into the `auth` object of every proxied request body — you only need to supply the `serviceId` and other fields for the specific SAGE Member module you're calling.

Or fetch credentials with the [Node SDK](/reference/sdks/node#get-a-connection-with-credentials) or [API](/reference/api/connection/get).

You're connected! Check the [Logs](https://app.nango.dev/dev/logs) tab in Nango to inspect requests.
</Step>

<Step id="implement-in-app" title="Implement Nango in your app">
Follow our [Auth implementation guide](/guides/primitives/auth) to integrate Nango in your app.

To obtain your own production credentials, follow the setup guide linked below.
</Step>
</Steps>

## SAGE Member Integration guides

Nango-maintained guides for common use cases.

- [How do I link my account?](/api-integrations/sage-member/connect)
Connect your SAGE Member account using the Connect UI

## Pre-built syncs & actions for SAGE Member

Enable them in your dashboard. Extend and customize to fit your needs.

import PreBuiltUseCases from "/snippets/generated/sage-member/PreBuiltUseCases.mdx"

<PreBuiltUseCases />

---
40 changes: 40 additions & 0 deletions docs/api-integrations/sage-member/connect.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
title: SAGE Member - How do I link my account?
sidebarTitle: SAGE Member
---

# Overview

To authenticate with SAGE Member, you need:
1. **Account Number** — Your SAGE Member account number.
2. **Login ID** — The login ID of the SAGE Member user account making the requests.
3. **Authentication Key** — The key associated with that user's login, used to authenticate every request.

### Prerequisites

- An active SAGE Workplace subscription or Supplier Advantage membership.
- A SAGE Member module enabled on your account (enable modules on the Services tab).

### Step 1: Finding your credentials

SAGE Member authenticates on a per-user basis, so each set of credentials is tied to one user on your account.

1. Log in to your SAGE account and go to **Developer APIs** > **SAGE Connect**.
2. Go to the **Users** tab.
3. Click your profile in the top right to find your **Account Number**.
4. Select the user that will be making API requests and note their **Login ID** and **Authentication Key**.

<img src="/api-integrations/sage-member/users_tab.png"/>
Comment thread
hassan254-prog marked this conversation as resolved.


### Step 2: Enter credentials in the Connect UI

Once you have your credentials:
1. Open the form where you need to authenticate with SAGE Member.
2. Enter your **Account Number**, **Login ID**, and **Authentication Key** in their designated fields.
3. Submit the form, and you should be successfully authenticated.

<img src="/api-integrations/sage-member/form.png" style={{maxWidth: "450px" }}/>
Comment thread
hassan254-prog marked this conversation as resolved.


You are now connected to SAGE Member.
Binary file added docs/api-integrations/sage-member/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/api-integrations/sage-member/users_tab.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -1554,6 +1554,7 @@
"integrations/all/sage-intacct",
"integrations/all/sage-intacct-oauth",
"api-integrations/sage-intacct-cc",
"api-integrations/sage-member",
"integrations/all/sage-people",
"api-integrations/sap-ariba",
"api-integrations/salesforce",
Expand Down
1 change: 1 addition & 0 deletions docs/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14560,6 +14560,7 @@ Use these slugs to construct provider-specific docs URLs only when needed.
| `sage-intacct` | Sage Intacct | TWO_STEP | [docs](https://nango.dev/docs/integrations/all/sage-intacct.md) | [connect](https://nango.dev/docs/integrations/all/sage-intacct/connect.md) | | accounting, erp |
| `sage-intacct-cc` | Sage Intacct (Client Credentials) | TWO_STEP | [docs](https://nango.dev/docs/api-integrations/sage-intacct-cc.md) | [connect](https://nango.dev/docs/api-integrations/sage-intacct-cc/connect.md) | [setup](https://nango.dev/docs/api-integrations/sage-intacct-cc/connect.md) | accounting, erp |
| `sage-intacct-oauth` | Sage Intacct (OAuth) | OAUTH2 | [docs](https://nango.dev/docs/integrations/all/sage-intacct-oauth.md) | | | accounting, erp, popular |
| `sage-member` | SAGE Member | API_KEY | [docs](https://nango.dev/docs/api-integrations/sage-member.md) | [connect](https://nango.dev/docs/api-integrations/sage-member/connect.md) | | marketing, e-commerce |
| `sage-people` | Sage People | OAUTH2 | [docs](https://nango.dev/docs/integrations/all/sage-people.md) | [connect](https://nango.dev/docs/integrations/all/sage-people/connect.md) | | hr |
| `salesforce` | Salesforce | OAUTH2 | [docs](https://nango.dev/docs/api-integrations/salesforce.md) | [connect](https://nango.dev/docs/api-integrations/salesforce/connect.md) | [setup](https://nango.dev/docs/api-integrations/salesforce/salesforce-api-oauth-app-setup.md) | crm, popular |
| `salesforce-cc` | Salesforce (Client Credentials) | OAUTH2_CC | [docs](https://nango.dev/docs/api-integrations/salesforce-cc.md) | [connect](https://nango.dev/docs/api-integrations/salesforce-cc/connect.md) | | crm |
Expand Down
2 changes: 1 addition & 1 deletion docs/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ Use provider URLs by replacing `{slug}` with a slug from the API catalog:

## APIs and integrations

- [API catalog](https://nango.dev/docs/api-catalog.txt): 874 provider slugs with canonical docs routes, auth modes, setup guides, and connect guides.
- [API catalog](https://nango.dev/docs/api-catalog.txt): 875 provider slugs with canonical docs routes, auth modes, setup guides, and connect guides.
- Provider-specific pages are intentionally not expanded here so core Nango guides remain easy for agents to find.

## Resources
Expand Down
3 changes: 3 additions & 0 deletions docs/snippets/generated/sage-member/PreBuiltUseCases.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
_No pre-built syncs or actions available yet._

<Tip>Not seeing the integration you need? [Build your own](/guides/functions/functions-guide) independently.</Tip>
40 changes: 40 additions & 0 deletions packages/providers/providers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17633,6 +17633,46 @@ sage-intacct:
description: Your Sage Intacct User Password
secret: true

sage-member:
display_name: SAGE Member
categories:
- marketing
- e-commerce
auth_mode: API_KEY
proxy:
base_url: https://www.promoplace.com/ws/ws.dll

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that doesn't look like SAGE related?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what they have in their gated docs.
Screenshot 2026-07-10 at 17 17 38

body:
auth:
acctId: ${connectionConfig.acctId}
loginId: ${connectionConfig.loginId}
key: ${apiKey}
docs: https://nango.dev/docs/api-integrations/sage-member
docs_connect: https://nango.dev/docs/api-integrations/sage-member/connect
credentials:
apiKey:
type: string
title: Authentication Key
example: 56edbbe3********************141a
pattern: '^[a-f0-9]{32}$'
description: The authentication key for your SAGE Member user.
secret: true
doc_section: '#step-1-finding-your-credentials'
connection_config:
acctId:
type: string
title: Account Number
description: Your SAGE Member account number.
pattern: '^[0-9]+$'
example: '4353478'
order: 1
doc_section: '#step-1-finding-your-credentials'
loginId:
type: string
title: Login ID
description: The login ID for your SAGE Member user.
order: 2
doc_section: '#step-1-finding-your-credentials'

sage-people:
display_name: Sage People
categories:
Expand Down
54 changes: 45 additions & 9 deletions packages/shared/lib/services/proxy/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,25 +509,47 @@ export function buildProxyBody({
}: {
config: ApplicationConstructedProxyConfiguration;
connection: ConnectionForProxy;
}): Record<string, string> | null {
}): Record<string, string | Record<string, string>> | null {
if (!config.provider?.proxy?.body) {
return null;
}

const body: Record<string, string> = {};
const replacers = {
connectionConfig: connection.connection_config,
credentials: connection.credentials,
...(connection.credentials as unknown as Record<string, string>)
};

const interpolateLeaf = (value: string): string | null => {
const interpolated = interpolateIfNeeded(value, replacers);
return interpolated.includes('${') ? null : interpolated;
};

const body: Record<string, string | Record<string, string>> = {};
for (const [key, value] of Object.entries(config.provider.proxy.body)) {
if (typeof value !== 'string') {
if (typeof value === 'string') {
const interpolated = interpolateLeaf(value);
if (interpolated !== null) {
body[key] = interpolated;
}
continue;
}
const interpolated = interpolateIfNeeded(value, replacers);
if (!interpolated.includes('${')) {
body[key] = interpolated;

if (!isPlainObject(value)) {
continue;
}
const nested: Record<string, string> = {};
for (const [nestedKey, nestedValue] of Object.entries(value)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if there is more than one level of nesting?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated this in the latest commit to handle recursive object mapping instead of only supporting a single level of nested objects.

if (typeof nestedValue !== 'string') {
continue;
}
const interpolated = interpolateLeaf(nestedValue);
if (interpolated !== null) {
nested[nestedKey] = interpolated;
}
}
if (Object.keys(nested).length > 0) {
body[key] = nested;
}
}

Expand All @@ -545,15 +567,29 @@ function isPlainObject(value: unknown): value is Record<string, unknown> {
);
}

function mergeInjectedBody(existing: unknown, injected: Record<string, string>): unknown {
function appendInjectedEntry(append: (key: string, value: string) => void, key: string, value: string | Record<string, string>): void {
if (typeof value === 'string') {
append(key, value);
return;
}
for (const [nestedKey, nestedValue] of Object.entries(value)) {
append(`${key}[${nestedKey}]`, nestedValue);
}
}

function mergeInjectedBody(existing: unknown, injected: Record<string, string | Record<string, string>>): unknown {
if (!existing) return injected;
if (isPlainObject(existing)) return { ...existing, ...injected };
if (existing instanceof FormData) {
for (const [key, value] of Object.entries(injected)) existing.append(key, value);
for (const [key, value] of Object.entries(injected)) {
appendInjectedEntry((k, v) => existing.append(k, v), key, value);
}
return existing;
}
if (existing instanceof URLSearchParams) {
for (const [key, value] of Object.entries(injected)) existing.append(key, value);
for (const [key, value] of Object.entries(injected)) {
appendInjectedEntry((k, v) => existing.append(k, v), key, value);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have proper testing for all the existing code paths that are being affected by this change

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we have proper testing for this.

}
return existing;
}
return existing;
Expand Down
Loading
Loading