# How to Integrate Bolt.new with Marketo

- Tool: Bolt.new
- Difficulty: Intermediate
- Time required: 40 minutes
- Last updated: April 2026

## TL;DR

Integrate Bolt.new with Marketo (Adobe) using the Marketo REST API with OAuth 2.0 client credentials. Get your API credentials from Marketo Admin → Integration → LaunchPoint, build a Next.js API route that fetches an access token, and call the REST API to manage leads, query campaign performance, and sync form submissions. Requires an active Marketo subscription. Rate limit is 50,000 calls per day.

## Building Marketo Marketing Automation Features in Bolt.new Apps

Marketo's REST API exposes the full marketing automation platform programmatically: lead database management, campaign triggers, email performance metrics, scoring rules, and activity history. For Bolt.new developers building B2B SaaS apps or marketing tools, the most common integration scenarios are syncing leads from web forms into the Marketo database, triggering campaign workflows when users reach specific milestones, and building custom reporting dashboards that surface Marketo data in a more relevant context than Marketo's native analytics.

Marketo's API authentication uses the OAuth 2.0 client credentials flow — sometimes called two-legged OAuth — where your server exchanges a client ID and secret for a time-limited access token. There is no user login required. This means the integration works purely server-to-server, which aligns well with Bolt's API route pattern. You request a token, cache it for its one-hour lifetime, and include it in every API request.

Marketo is an enterprise product with subscription pricing — integrating it requires an active Marketo subscription where the administrator can create a LaunchPoint service. If you are evaluating whether to build a Marketo integration, confirm that your organization has Marketo access and that your Marketo admin can create an API user and LaunchPoint service before investing time in the implementation. The daily call limit is 50,000 API calls, which is generous for most integration patterns.

## Before you start

- An active Marketo subscription (trial or paid) with administrator access to create LaunchPoint services
- A Marketo API-Only user account created by your Marketo admin (Admin → Security → Users → Invite User → select API Only)
- A LaunchPoint service created in Marketo Admin → Integration → LaunchPoint with the API-Only user assigned
- Your Marketo REST API endpoint URL (found in Admin → Integration → Web Services → REST API → Endpoint)
- Client ID and Client Secret from the LaunchPoint service detail view

## Step-by-step guide

### 1. Create a Marketo LaunchPoint service and get API credentials

Marketo's API access is managed through the LaunchPoint service registry. Before you can make any API calls, a Marketo administrator must create an API-only user account and a corresponding LaunchPoint service that generates the client credentials.

Step 1: Create an API-only user. In Marketo, go to Admin → Security → Users → Invite User. Enter an email address for the API user (can be a service account email), check 'API Only', and assign the appropriate role — at minimum the 'Read-Write Lead' and 'Read-Write Assets' permissions. Complete the invitation.

Step 2: Create a LaunchPoint service. Go to Admin → Integration → LaunchPoint → New → New Service. Give the service a name (e.g., 'Bolt App Integration'), set the service type to 'Custom', and select the API-only user you just created. Save the service.

Step 3: Get credentials. On the LaunchPoint services list, find your new service and click 'View Details'. You will see the Client ID and Client Secret. Copy both.

Step 4: Find your REST API endpoint. Go to Admin → Integration → Web Services. Under the REST API section, you will see the Endpoint URL — it looks like https://[instance-id].mktorest.com/rest. Copy this URL. You will also need the Identity URL shown there, which is used for token requests.

Store all four values in your Bolt project's .env.local file.

```
// .env.local
MARKETO_CLIENT_ID=your_client_id_here
MARKETO_CLIENT_SECRET=your_client_secret_here
MARKETO_REST_ENDPOINT=https://your-instance-id.mktorest.com/rest
MARKETO_IDENTITY_URL=https://your-instance-id.mktorest.com/identity
```

**Expected result:** All four Marketo credentials are stored in .env.local and the getMarketoToken() function successfully returns an access token when called from a server-side route.

### 2. Build the token utility and lead upsert API route

The Marketo token request uses the Identity URL with GET parameters: grant_type=client_credentials, client_id, and client_secret. Despite being a GET request (not POST like most OAuth implementations), Marketo uses GET for token requests — this is a Marketo-specific quirk worth noting. The token response includes access_token and expires_in (seconds, typically 3600).

Cache the token in server memory alongside its expiry timestamp. Check expiry before every API call and refresh if within 60 seconds of expiry. In a Next.js serverless environment, each function invocation may have its own memory, so the cache may not persist between requests — implement either a fast token cache (Redis or Upstash) for production or simply accept the small overhead of re-fetching tokens.

The most fundamental Marketo operation is the lead upsert: creating a new lead if none exists with that email, or updating an existing lead's fields. Use POST /rest/v1/leads.json with action: 'createOrUpdate', lookupField: 'email', and an input array of lead objects. This single endpoint handles both creates and updates with deduplication on email address. The response includes a result array with each lead's id, status ('created', 'updated', or 'skipped'), and any errors.

```
// lib/marketo.ts
let tokenCache: { token: string; expiresAt: number } | null = null;

export async function getMarketoToken(): Promise<string> {
  if (tokenCache && Date.now() < tokenCache.expiresAt - 60_000) {
    return tokenCache.token;
  }

  const identityUrl = process.env.MARKETO_IDENTITY_URL!;
  const clientId = process.env.MARKETO_CLIENT_ID!;
  const clientSecret = process.env.MARKETO_CLIENT_SECRET!;

  // Note: Marketo token request uses GET (not POST) with query parameters
  const response = await fetch(
    `${identityUrl}/oauth/token?grant_type=client_credentials&client_id=${clientId}&client_secret=${clientSecret}`
  );

  if (!response.ok) throw new Error(`Marketo token request failed: ${response.status}`);

  const data = await response.json();
  if (data.error) throw new Error(`Marketo auth error: ${data.error_description}`);

  tokenCache = {
    token: data.access_token,
    expiresAt: Date.now() + data.expires_in * 1000,
  };
  return tokenCache.token;
}

export async function marketoFetch(
  path: string,
  options: RequestInit = {}
): Promise<Response> {
  const token = await getMarketoToken();
  const baseUrl = process.env.MARKETO_REST_ENDPOINT!;
  return fetch(`${baseUrl}${path}`, {
    ...options,
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
      ...(options.headers ?? {}),
    },
  });
}

// app/api/marketo/leads/upsert/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { marketoFetch } from '@/lib/marketo';

interface MarketoLead {
  email: string;
  firstName?: string;
  lastName?: string;
  company?: string;
  title?: string;
  [key: string]: unknown;
}

export async function POST(request: NextRequest) {
  const body = await request.json();
  const leads: MarketoLead[] = Array.isArray(body) ? body : [body];

  const response = await marketoFetch('/v1/leads.json', {
    method: 'POST',
    body: JSON.stringify({
      action: 'createOrUpdate',
      lookupField: 'email',
      input: leads,
    }),
  });

  if (!response.ok) {
    return NextResponse.json({ error: `Marketo API error: ${response.status}` }, { status: response.status });
  }

  const data = await response.json();

  if (!data.success) {
    return NextResponse.json({ error: data.errors }, { status: 400 });
  }

  return NextResponse.json({
    results: data.result,
    requestId: data.requestId,
  });
}
```

**Expected result:** Calling /api/marketo/leads/upsert with a lead object containing an email address successfully creates or updates the lead in Marketo and returns the lead ID and status.

### 3. Query leads and build a lead management dashboard

Querying leads in Marketo requires understanding the filter type system. The GET /rest/v1/leads.json endpoint accepts filterType (the field to filter on) and filterValues (comma-separated values). The most common filter types are email (by email address), id (by Marketo lead ID), and leadPartitionId (all leads in a partition). Custom fields can also be used as filter types if they are indexed.

The fields parameter controls which lead attributes are returned — always specify exactly the fields you need rather than returning all fields, as Marketo lead records can have hundreds of custom fields and returning all of them significantly increases response size and latency.

For dashboard use cases that require sorting and paging across large lead databases, the bulk extract API is more appropriate than the filter API. However, bulk extract is asynchronous (you submit a job, wait for it to complete, then download the result file) and adds significant complexity. For dashboards showing the top N leads by score, using the filter API with the lead score filter type and a reasonable limit is simpler and works well for most use cases.

Marketo's activity types API provides the historical record of what leads have done: email opens, web page visits, form fills, score changes. To get a lead's activity, first call /rest/v1/activities/leadchanges.json or /rest/v1/activities.json with the lead ID and a paging token. Activity queries require a paging token obtained from /rest/v1/activities/pagingtoken.json, which is a date-based starting point.

```
// app/api/marketo/leads/query/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { marketoFetch } from '@/lib/marketo';

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const emails = searchParams.get('emails'); // comma-separated
  const filterType = searchParams.get('filterType') ?? 'email';

  if (!emails) {
    return NextResponse.json({ error: 'emails query parameter is required' }, { status: 400 });
  }

  const fields = [
    'id', 'email', 'firstName', 'lastName', 'company', 'title',
    'leadScore', 'leadStatus', 'updatedAt', 'createdAt',
  ].join(',');

  const response = await marketoFetch(
    `/v1/leads.json?filterType=${filterType}&filterValues=${encodeURIComponent(emails)}&fields=${fields}`
  );

  if (!response.ok) {
    return NextResponse.json({ error: `Marketo API error: ${response.status}` }, { status: response.status });
  }

  const data = await response.json();

  if (!data.success) {
    return NextResponse.json({ error: data.errors }, { status: 400 });
  }

  return NextResponse.json({
    leads: data.result,
    total: data.result?.length ?? 0,
    requestId: data.requestId,
  });
}
```

**Expected result:** Querying /api/marketo/leads/query?emails=user@example.com returns the lead record with score, contact info, and timestamps from Marketo.

## Best practices

- Cache Marketo access tokens in memory with their expiry time — tokens last one hour and re-requesting them unnecessarily reduces your 50,000 daily API call budget.
- Always specify the 'fields' parameter in lead queries — returning all fields by default includes hundreds of potentially empty custom fields and significantly increases response size.
- Use 'createOrUpdate' action for all form-to-lead sync operations — it handles both new leads and returning contacts without requiring separate create and update logic.
- Store MARKETO_CLIENT_ID and MARKETO_CLIENT_SECRET in server-side environment variables only — never prefix them with NEXT_PUBLIC_ as that would expose them in the browser bundle.
- Map your application's custom data to Marketo custom fields using the API field names (not the display names) — field names are case-sensitive in Marketo's REST API.
- Keep track of your daily API call usage through Marketo Admin → Integration → Web Services which shows current usage — hitting the 50,000 daily limit causes all API calls to return 606 errors until midnight.
- For high-volume scenarios (thousands of leads per day), consider Marketo's bulk import API as an alternative to the transactional upsert API — bulk import is asynchronous but more efficient and does not count against the daily REST API call limit.

## Use cases

### Lead capture form sync

When a user submits a contact or sign-up form in your Bolt app, automatically create or update a lead in Marketo with their contact information, company, and any custom fields that map to your lead scoring criteria. Marketo's lead scoring and nurture workflows activate automatically based on the lead's attributes.

Prompt example:

```
Add a lead sync to my contact form. When the form is submitted with name, email, company, and job title, call /api/marketo/leads/upsert to create or update the lead in Marketo. Map the form fields to Marketo's standard fields: Email, FirstName, LastName, Company, Title. Return success or a specific error if the email is already in a suppression list.
```

### Lead scoring dashboard

Build an internal dashboard showing your top leads ranked by Marketo lead score, with each lead's recent activity (email opens, web visits, form fills) listed alongside their score. Sales reps can prioritize outreach without logging into Marketo directly.

Prompt example:

```
Create a lead scoring dashboard page. Fetch leads from the Marketo API filtered by lead score greater than 50, sorted by score descending, with a limit of 50. Display them in a table with columns for name, email, company, lead score, and last activity date. Add a 'View in Marketo' link for each lead.
```

### Campaign performance analytics

Surface email campaign metrics — open rate, click rate, bounce rate, unsubscribes — from Marketo into a custom analytics page that also shows revenue attribution from your CRM. Marketing teams can see performance without switching between tools.

Prompt example:

```
Build a Marketo campaign analytics page. Fetch the list of recent email programs from the Marketo API and for each program fetch the email performance summary with sent count, open rate, click rate, and unsubscribe rate. Display the data as a table with sortable columns and sparkline trend indicators for open and click rates.
```

## Troubleshooting

### Token request returns 401 with error 'unauthorized' or '1000: Access token invalid'

Cause: The client ID or client secret is incorrect, the LaunchPoint service is not active, or the API-only user account has been deactivated or is not assigned to the service.

Solution: In Marketo Admin → Integration → LaunchPoint, click 'View Details' on your service to verify the client ID shown there matches your MARKETO_CLIENT_ID. Check that the API-only user account assigned to the service is active (Admin → Security → Users). If credentials look correct, try creating a new LaunchPoint service and generating new credentials.

### Lead upsert returns success:false with error '1004: Lead not found' even for new leads

Cause: Using action: 'updateOnly' instead of 'createOrUpdate' — updateOnly fails if the lead does not exist. Alternatively, the lookupField value specified is not indexed in Marketo.

Solution: Always use action: 'createOrUpdate' for form sync scenarios. If a lead with the lookup email exists, it will be updated; if not, a new lead is created. The 'updateOnly' action is only appropriate when you know the lead already exists and want to prevent accidental creation.

```
// Use createOrUpdate to handle both new and existing leads:
body: JSON.stringify({
  action: 'createOrUpdate', // Not 'updateOnly'
  lookupField: 'email',
  input: leads,
})
```

### API returns 413 or times out when trying to sync a large number of leads

Cause: The Marketo REST API enforces a 300-lead maximum per request for the lead upsert endpoint. Sending more than 300 leads in a single request causes an error.

Solution: Split the leads array into chunks of 300 and process them sequentially or in controlled batches. Add a small delay between batches to avoid rate limit issues on high-volume syncs.

```
// Chunk leads into batches of 300:
async function upsertLeadsBatch(leads: MarketoLead[]) {
  const chunkSize = 300;
  const results = [];
  for (let i = 0; i < leads.length; i += chunkSize) {
    const chunk = leads.slice(i, i + chunkSize);
    const result = await marketoFetch('/v1/leads.json', {
      method: 'POST',
      body: JSON.stringify({ action: 'createOrUpdate', lookupField: 'email', input: chunk }),
    });
    results.push(await result.json());
    if (i + chunkSize < leads.length) {
      await new Promise(resolve => setTimeout(resolve, 100)); // Small delay between batches
    }
  }
  return results;
}
```

## Frequently asked questions

### Can I test the Marketo integration in Bolt's preview before deploying?

Yes. Marketo's REST API uses client credentials OAuth (no user login required) and all calls are outbound HTTP requests from your server-side API routes. Both work in Bolt's WebContainer development preview. The only feature that requires deployment is if you want Marketo to push webhook events to your app — Marketo can send webhooks for lead score changes and other events, but the webhook endpoint must be a deployed public URL.

### Does Bolt.new have a native Marketo integration?

No. Marketo is not one of Bolt's native connectors. You integrate it by building Next.js API routes that call the Marketo REST API. Bolt's AI can generate the integration code when you describe your lead management or campaign analytics requirements.

### What Marketo plan or role do I need to create API credentials?

You need Marketo administrator access to create an API-only user and a LaunchPoint service. Standard users cannot access the Admin → Integration → LaunchPoint menu. If you are not the Marketo admin, you will need to request that your admin create a LaunchPoint service and share the Client ID and Client Secret with you.

### What is the Marketo REST API daily call limit?

The standard limit is 50,000 API calls per day, which resets at midnight in your Marketo instance's timezone. Enterprise contracts may have higher limits. You can monitor current usage in Admin → Integration → Web Services. Exceeding the limit returns error code 606 for all subsequent calls until the daily reset.

### Can I use the Marketo API to send emails or trigger campaigns?

Yes. You can trigger campaign execution using POST /rest/v1/campaigns/{id}/trigger.json with a lead ID. This invokes a trigger-based Smart Campaign that has the 'Campaign is Requested (via Web Service API)' trigger. You can also schedule batch campaigns using POST /rest/v1/campaigns/{id}/schedule.json. For transactional single emails, the Transactional Email endpoint /rest/v1/email/sendSample.json is available on some Marketo plans.

---

Source: https://www.rapidevelopers.com/bolt-ai-integrations/marketo
© RapidDev — https://www.rapidevelopers.com/bolt-ai-integrations/marketo
