Why Pardot OAuth Setup Happens in Salesforce, Not Pardot
Every Pardot (Account Engagement) API integration starts in the same place, and it is not Pardot. Since Salesforce retired legacy user key authentication on February 15, 2021, the only way into the API at pi.pardot.com is a Salesforce OAuth 2.0 access token issued through an app you configure in Salesforce Setup, plus a Pardot-Business-Unit-Id header on every request. Get that app wrong and nothing downstream works: not the sync, not the middleware, not the Apex callout.
There is also a 2026-specific trap. As of Spring '26, newly provisioned orgs cannot create Connected Apps at all; the 'Allow creation of connected apps' setting can only be enabled by Salesforce Support, and future releases will remove even that. Existing Connected Apps keep working, but new builds belong on External Client Apps, the successor, which is the path this tutorial follows.
This article is the zoomed-in first step of our full technical guide at cloudsheer.com/integrations/pardot/technical-guide. Cloudsheer has shipped this integration across 25 client projects, and the OAuth app is where most first-week failures happen: a renamed scope, a missing header, a token requested from the wrong endpoint.
What You Need Before You Start
- Salesforce admin access to Setup in an org with Account Engagement enabled. Both the External Client App screens and the Business Unit ID page are admin-only.
- The name of the target Account Engagement business unit. Multiple units mean multiple 18-character IDs, and the token's user needs a role in the specific one you call.
- A dedicated integration user, or the license to create one. The classic pattern is an Identity license (Salesforce provisioned 100 free ones to Account Engagement customers) or a full CRM license.
- A decision on OAuth flow: client credentials for server-to-server integrations with no human present (most Pardot syncs), or web server flow if a person will log in through a browser.
- A password manager ready for the Consumer Key and Consumer Secret.
- Access to the email inbox on your Salesforce user record; retrieving credentials requires an emailed identity verification code.
Step 1: Create the External Client App
Click the gear icon, then Setup. In the Quick Find box type External Client Apps and click External Client App Manager. Click New External Client App.
- Fill in Basic Info: External Client App Name (something searchable like Pardot API Middleware), API Name (auto-fills), and Contact Email.
- Under Distribution State choose Local. You are building for this org, not packaging for distribution.
- Do not start a new build under App Manager and New Connected App; Spring '26 orgs cannot create Connected Apps at all.
Step 2: Enable OAuth and Select Exactly the Right Scopes
Expand the API (Enable OAuth Settings) section and check Enable OAuth. This is where most Pardot integrations quietly break, so slow down here.
- Callback URL: enter your redirect URI. For web server flow this must exactly match what your code sends later, down to protocol, domain, port, path, and trailing slash. For client credentials the field is required but never used; a valid placeholder like https://notused.example.com is fine.
- Under OAuth Scopes, search the picker for Pardot and move Manage Pardot services (pardot_api) into Selected OAuth Scopes. Older tutorials call this scope Access Pardot Services; it was renamed, so match on the pardot_api in parentheses.
- Web server flow with long-lived access: also add Perform requests at any time (refresh_token, offline_access).
- Client credentials: do NOT add refresh_token, offline_access. That scope is only valid for user-delegated flows and causes an authentication error on the token request.
- Skip Manage user data via APIs (api) unless this same app also calls the core Salesforce REST or SOAP APIs. pardot_api alone covers everything at pi.pardot.com.
- For server-to-server, check Enable Client Credentials Flow. Then click Create.
Now start a timer. Per the official Account Engagement quick start, an External Client App can take up to 15 minutes before authentication works. A failed token request in the first quarter hour is expected, not broken.
Step 3: Set OAuth Policies and Retrieve the Consumer Key and Secret
Creating the app is not enough for client credentials. Open it in External Client App Manager and edit its policies.
- Under OAuth Policies, set Permitted Users to Admin approved users are pre-authorized.
- Set the Client Credentials Flow Run As user to your integration user. If that user does not exist yet, do Step 4 first and come back.
- Re-confirm the Enable Client Credentials Flow checkbox while editing policies. Community walkthroughs consistently note it must be checked both at creation and again here.
- Under App Policies, grant app access through a permission set assigned to the integration user.
- To pull credentials: Settings, then OAuth Settings, click Consumer Key and Secret. Salesforce emails a verification code; enter it and the page shows both values for roughly 5 minutes before re-verification. Copy both into a password manager immediately and treat the secret like a production password from this second onward: no email, no Slack, no spreadsheet.
Step 4: Create the Integration User and Find Your Business Unit ID
The v5 API enforces the calling user's Account Engagement role on every endpoint, so the user behind your token matters as much as the app.
- Create a dedicated Salesforce user for this integration. Salesforce recommends one per integration for traceability.
- Make that user an Account Engagement user mapped to the target business unit, with a role. Administrator gives the full API surface; a read-only Sales role fails on create and update calls even with a perfect token.
- Confirm the user can open the Account Engagement Lightning app; the API user must be SSO-capable, not a login-blocked shell.
- Find the Business Unit ID: Setup, Quick Find, Pardot Account Setup (Salesforce Help article 000381973 also lists Business Unit Setup). The ID sits next to each business unit, starts with 0Uv, and is 18 characters. Only admins see this page; developers without Setup access should request it now.
Step 5: Wire It Into Salesforce with an External Credential and Named Credential
If you call the API from Apex or Flow, do not hand-roll token management. This is the Salesforce-side wiring we deploy on client projects.
- Setup, Quick Find, Named Credentials, then the External Credentials tab, then New. Authentication Protocol: OAuth 2.0. Authentication Flow Type: Client Credentials with Client Secret.
- Identity Provider URL: your My Domain token endpoint, https://yourdomain.my.salesforce.com/services/oauth2/token (find it at Setup, Quick Find, My Domain). login.salesforce.com does not work for this grant.
- Check Pass client credentials in request body, then store the Consumer Key as client ID and the Consumer Secret as client secret on the credential's Principal.
- Create a Named Credential pointing at https://pi.pardot.com and link it to the external credential.
- Grant access to the Principal through a permission set assigned to every user that runs the callout.
- Only add an Auth Provider if you chose the browser-based web server flow. Client credentials never needs one.
Step 6: Smoke Test Against a Real Endpoint
Prove the whole chain before writing any integration logic.
- Request a token: POST to https://yourdomain.my.salesforce.com/services/oauth2/token with the body grant_type=client_credentials&client_id=YOUR_CONSUMER_KEY&client_secret=YOUR_CONSUMER_SECRET. A JSON response containing access_token proves the app, policies, and Run As user are correct.
- Call the API: GET https://pi.pardot.com/api/v5/objects/prospects?fields=id,email with two headers, Authorization: Bearer YOUR_ACCESS_TOKEN and Pardot-Business-Unit-Id: 0Uv... (your 18-character ID). A JSON body listing prospects is success.
- Developer Edition orgs and Account Engagement sandbox business units use pi.demo.pardot.com instead. Tokens are not interchangeable between the two hostnames.
- Plan for expiry: token validity follows your session timeout policy, commonly 2 hours. Client credentials never issues a refresh token; on a 401, request a fresh one from the My Domain endpoint. Web server flow renews with grant_type=refresh_token.
Common Errors and How to Fix Them
- HTTP 401, access_token is invalid, unknown, or malformed: check, in order, the app has the Manage Pardot services (pardot_api) scope, the token's user has an Account Engagement role in the business unit in your header, the token has not hit session timeout, and you are not mixing production tokens with pi.demo.pardot.com.
- HTTP 400, A required header is missing: you left out Pardot-Business-Unit-Id. Every call fails without it, even with a valid token.
- Business Unit specified in Pardot-Business-Unit-Id header not found or inactive: a typo in the 0Uv ID, an inactive business unit, or the right ID sent to the wrong hostname.
- invalid_grant on the client credentials token request: no Run As user set, the Run As user not pre-authorized through the permission set, or the request went to login.salesforce.com instead of your My Domain endpoint.
- Authentication error when requesting a client credentials token: the app still has refresh_token, offline_access selected. Remove it; that scope is only for user-delegated flows.
- error=redirect_uri_mismatch on the authorize call: your redirect_uri does not exactly match a saved Callback URL. Compare protocol, domain, port, path, trailing slash, and case.
- err code 122, Daily API rate limit met: you exhausted the daily allowance, 25,000 requests per day on Growth up to 100,000 on top editions, resetting daily in the account's time zone.
- Authentication using an api_key is no longer supported: your code uses the legacy user_key pattern retired on February 15, 2021. Move to the Salesforce OAuth bearer token described above.
Security Checklist Before You Ship
- The Consumer Secret lives only in a secrets manager or the External Credential, never in code, email, or chat. Salesforce supports in-console rotation if it leaks.
- The integration runs as a dedicated user with the least Account Engagement role that covers its endpoints, not as a human admin's account.
- Permitted Users is Admin approved users are pre-authorized, with access granted through a named permission set you can audit.
- The scope list is minimal: pardot_api, plus refresh_token, offline_access only on web server flow apps, plus api only if the app genuinely calls core Salesforce APIs.
- For web server flow apps, the Refresh Token Policy is a deliberate choice; the default is valid until revoked, so consider an inactivity expiry.
- Callback URLs contain only HTTPS production endpoints; localhost entries from development have been removed.
Where This Fits in the Full Pardot (Account Engagement) Build
OAuth is step one of a real Pardot (Account Engagement) integration, not the whole job. You still need to design sync direction, map fields across leads, contacts, and prospects, budget the daily API limit, and decide what happens when the same email exists in two business units. The complete build sequence lives in our technical guide at cloudsheer.com/integrations/pardot/technical-guide.
If you build with Claude Code, we packaged this playbook as a free skill. Run npx skills add shivamgoel-cloudsheer/Claude-skills --skill pardot-salesforce and your agent gets the same click paths, scope names, header requirements, and error fixes as context while it writes your integration code.
How Cloudsheer Can Help
Cloudsheer is a Salesforce consultancy that has shipped the Pardot (Account Engagement) integration pattern across 25 client projects, from single business unit prospect syncs to multi-unit builds. We have hit every error in the list above on someone else's timeline, which is exactly why yours does not have to.
If you want this stood up correctly the first time, with the External Client App, integration user, credential wiring, and smoke tests done and documented, we can usually complete the OAuth foundation in a day and the full integration in a sprint. Book a free discovery call at cal.com/cloudsheer-consulting/30min.
