2Slides Logo
Personalized Sales Proposals at Scale: Google Sheets + API (2026)
2Slides Team
17 min read

Personalized Sales Proposals at Scale: Google Sheets + API (2026)

To produce personalized sales proposals at scale, keep prospects in a Google Sheet (or a CRM export), turn each row into a structured prompt, and send it to the 2Slides Fast PPT endpoint POST /api/v1/slides/generate with one fixed themeId so every deck shares a design. Each 10-page proposal costs 100 credits, about $0.25, arrives as a native editable PPTX in roughly 30 seconds, and can be emailed to the rep automatically. This recipe is for RevOps and sales ops leads, and founders running outbound.

Key takeaways

  • One POST /api/v1/slides/generate call turns a spreadsheet row into a designed, editable PPTX proposal for 10 credits per page.
  • A fixed themeId from GET /api/v1/themes/search?query=pitch deck keeps 200 proposals visually identical, so reps only touch content.
  • responseLanguage accepts a language name per row ("Spanish", "Portuguese"), so LATAM and EMEA accounts get native-language decks from the same sheet.
  • The generate endpoint allows 6 requests per minute per key; a Python queue that sleeps between batches clears 200 proposals in about 70 minutes.
  • 500 free credits on signup cover 5 test proposals; 500 production proposals a month cost about $102.50 in credits.

Who this is for

You run RevOps or sales ops, or you are a founder doing outbound yourself. Reps spend 45 minutes hand-building a proposal deck that is 80% identical to the last one, so half of your qualified prospects never get a tailored proposal at all. Your prospect data already lives in a Google Sheet or a CRM export with company, industry, pain points, pricing tier, and contact name.

Do not use this recipe when each proposal needs custom visuals or narrated video. That is a job for the image-designed pipeline covered later. If your sales team lives inside HubSpot and you want proposals triggered from deal stages, read the CRM-native pattern in HubSpot AI presentation and sales deck automation instead.

The workflow at a glance

StepToolWhat happens2Slides endpoint / credits
1Google Sheets or CRM exportOne row per prospect: company, industry, pain points, pricing tier, contact, languagenone
22Slides Templates Hub or APIPick one pitch-deck template and record its themeIdGET /api/v1/themes/search, free
3Zapier Code step or PythonBuild a structured userInput prompt from the rownone
4Webhooks by Zapier or requestsSubmit the proposal job with mode: "async"POST /api/v1/slides/generate, 10 credits/page
5Delay 60 s, then GETPoll until status is successGET /api/v1/jobs/{jobId}, free
6Gmail step or SMTPEmail the editable PPTX downloadUrl to the repnone
7Google Sheets updateWrite jobId and downloadUrl back to the row for auditnone

Step 1: Structure the prospect sheet

The quality of the proposal is set by the columns you feed it. A flat sheet with one row per prospect and these columns is enough:

companyindustrycontact_namecontact_titlepain_pointspricing_tierpricelanguagerep_emailstatus
Northwind LogisticsFreight and logisticsMariana RuizVP OperationsManual dispatch scheduling; 18% empty-mile rate; no live ETAsGrowthUSD 1,800/monthSpanishj.ortiz@yourco.comready
Contoso ClinicsOutpatient healthcareDavid ChenCOOPaper intake forms; 22-minute average wait; no-show rate 14%TeamUSD 950/monthEnglisha.park@yourco.comready

Two rules make the downstream prompt reliable. First, write pain_points as short semicolon-separated phrases, not paragraphs, so the model gets three clear problems to address. Second, use the language column to hold a language name exactly as the API expects it ("Spanish", not "es"). A status column lets the automation skip rows already processed.

If you export from a CRM, map the same fields. The Google Sheets API can also read the sheet directly if you would rather not use CSV exports in the Python version below.

Step 2: Pick one themeId so every proposal shares a design

Consistency is the point of a proposal system. If every deck uses a different template, your brand looks like ten companies. Search the template library once, choose a pitch-deck or proposal template, and hard-code its themeId in the automation.

Browse visually first at 2slides.com/templates, filter by Pitch Deck, and note the template name.

2Slides Templates Hub with curated slide themes and Pitch Deck filter
Figure 1: The Templates Hub, filtered to Pitch Deck. Pick one template here, then fetch its themeId via the search endpoint.

Then resolve the same template to an ID from the API. The search endpoint is free and limited to 6 requests per minute, which is irrelevant here because you call it once.

curl -s "https://2slides.com/api/v1/themes/search?query=pitch%20deck&limit=5" \ -H "Authorization: Bearer $SLIDES_API_KEY"

The response lists matching templates with their themeId and metadata. Copy the ID into an environment variable (PROPOSAL_THEME_ID) so the Zapier step and the Python script read the same value. Change the template once a year, not per deal.

The API drives the same engine as the Fast PPT web tool: paste content, pick a template, get a PPTX. The endpoint simply does that headlessly for every row in your sheet.

2Slides Fast PPT interface with content box and template gallery
Figure 2: Fast PPT in the browser. The generate endpoint runs exactly this flow without the UI, which is what makes Google Sheets to PowerPoint automation possible.

Step 3: Build the prompt and call the sales proposal automation API

A proposal prompt should be structured, not conversational. Tell the model who the prospect is, what hurts, what you recommend, and what sections to produce in what order. Keep pricing on its own slide so reps can edit it without disturbing the rest of the deck.

Here is the exact call for the first row of the sheet above. Note responseLanguage comes from the row, and mode is "async" so the call returns immediately with a job ID.

curl -s -X POST https://2slides.com/api/v1/slides/generate \ -H "Authorization: Bearer $SLIDES_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "userInput": "Sales proposal for Northwind Logistics (freight and logistics). Prepared for Mariana Ruiz, VP Operations. Pain points: manual dispatch scheduling; 18% empty-mile rate; no live ETAs for customers. Recommended plan: Growth tier at USD 1,800/month for 25 dispatcher seats. Structure: 1) Our understanding of Northwind Logistics 2) Goals for the next 12 months 3) Proposed solution mapped to each pain point 4) 90-day rollout plan 5) Pricing (own slide) 6) Next steps. Consultative tone, no buzzwords.", "themeId": "'"$PROPOSAL_THEME_ID"'", "responseLanguage": "Spanish", "mode": "async" }'

Every 2Slides response is wrapped in the same envelope, which matters when you map fields in Zapier:

{ "success": true, "data": { "jobId": "a1b2c3d4", "status": "pending" } }

Poll the job every 5 to 10 seconds. Fast PPT decks usually finish in about 30 seconds.

curl -s https://2slides.com/api/v1/jobs/a1b2c3d4 \ -H "Authorization: Bearer $SLIDES_API_KEY" # { "success": true, "data": { "jobId": "a1b2c3d4", "status": "success", "downloadUrl": "https://..." } }

POST /api/v1/slides/generate parameters: themeId, userInput, responseLanguage, mode
Figure 3: The four parameters of the generate endpoint. themeId and userInput are required; responseLanguage defaults to Auto and mode defaults to sync.

If you leave mode out, the endpoint runs synchronously and returns downloadUrl in the same response. That is simpler for one-off calls, but async is safer when a platform like Zapier has a short HTTP timeout, and it is the right choice for batches.

Step 4: Zapier PowerPoint generation from a new sheet row

For teams without an engineer on call, Zapier gets a proposal into the rep's inbox within two minutes of a row being marked ready. The Webhooks by Zapier app makes the two API calls; everything else is standard steps.

  • Trigger: Google Sheets, new or updated row. Add a Filter step so only rows where status equals ready continue.
  • Code by Zapier (JavaScript) or Formatter: assemble userInput from the row columns (snippet below).
  • Webhooks by Zapier, POST to https://2slides.com/api/v1/slides/generate. Header Authorization: Bearer <your key>, JSON body with userInput, themeId, responseLanguage, mode: "async". Map data.jobId from the response.
  • Delay by Zapier, delay for 60 seconds.
  • Webhooks by Zapier, GET https://2slides.com/api/v1/jobs/{{jobId}} with the same header. Map data.status and data.downloadUrl.
  • Filter: continue only if data.status equals success. For the rare job still processing, a second Delay plus GET pair covers it.
  • Gmail: Send Email to rep_email with the prospect name in the subject and downloadUrl in the body or the attachment field.
  • Google Sheets: Update Row, write jobId, downloadUrl, and set status to done.

The Code step keeps prompt logic in one place, so a sales manager can change the proposal structure without touching the webhook:

// Code by Zapier (JavaScript). inputData fields are mapped from the sheet row. const { company, industry, contact_name, contact_title, pain_points, pricing_tier, price, language, } = inputData; const userInput = [ `Sales proposal for ${company} (${industry}).`, `Prepared for ${contact_name}, ${contact_title}.`, `Pain points: ${pain_points}.`, `Recommended plan: ${pricing_tier} tier at ${price}.`, `Structure: 1) Our understanding of ${company} 2) Goals for the next 12 months`, `3) Proposed solution mapped to each pain point 4) 90-day rollout plan`, `5) Pricing (own slide) 6) Next steps. Consultative tone, no buzzwords.`, ].join(" "); output = { userInput, responseLanguage: language || "English" };

Keep the API key in the Webhooks step header, never in the sheet. If several reps mark rows ready at the same time, Zapier may fire more than 6 generate calls in a minute; the extra calls receive a rate-limit error and Zapier's built-in replay handles them. For steadier volume, use the Python batch below. The same Zapier pattern is used for recurring decks in automating weekly report slides with Zapier and 2Slides.

Step 5: Batch 200 personalized sales proposals at scale with Python

When a campaign needs 200 proposals by Monday, run a script instead of firing 200 Zaps. The script below reads a CSV export of the sheet (File, Download, CSV), submits jobs in batches of 6 to respect the 6 requests per minute limit on generate, polls each job with a 6-second gap to stay under the 10 requests per minute limit on jobs, downloads each PPTX, and writes results to a second CSV your Gmail merge or CRM import can consume.

import csv, os, time, requests API = "https://2slides.com/api/v1" HEADERS = { "Authorization": f"Bearer {os.environ['SLIDES_API_KEY']}", "Content-Type": "application/json", } THEME_ID = os.environ["PROPOSAL_THEME_ID"] # from GET /themes/search?query=pitch deck BATCH = 6 # generate: 6 requests/min per key POLL_GAP = 6 # jobs: 10 requests/min -> one poll every 6 s OUT_DIR = "proposals" def build_prompt(row): return ( f"Sales proposal for {row['company']} ({row['industry']}). " f"Prepared for {row['contact_name']}, {row['contact_title']}. " f"Pain points: {row['pain_points']}. " f"Recommended plan: {row['pricing_tier']} tier at {row['price']}. " f"Structure: 1) Our understanding of {row['company']} 2) Goals for the next 12 months " f"3) Proposed solution mapped to each pain point 4) 90-day rollout plan " f"5) Pricing (own slide) 6) Next steps. Consultative tone, no buzzwords." ) def submit(row): r = requests.post(f"{API}/slides/generate", headers=HEADERS, timeout=60, json={ "userInput": build_prompt(row), "themeId": THEME_ID, "responseLanguage": row.get("language") or "English", "mode": "async", }) r.raise_for_status() body = r.json() # { success, data: { jobId, status } } if not body.get("success"): raise RuntimeError(body) return body["data"]["jobId"] def poll(job_id): r = requests.get(f"{API}/jobs/{job_id}", headers=HEADERS, timeout=30) r.raise_for_status() return r.json()["data"] # { jobId, status, progress, downloadUrl } def download(url, path): with requests.get(url, stream=True, timeout=120) as r: r.raise_for_status() with open(path, "wb") as f: for chunk in r.iter_content(65536): f.write(chunk) def main(): os.makedirs(OUT_DIR, exist_ok=True) with open("prospects.csv", newline="", encoding="utf-8") as f: rows = [r for r in csv.DictReader(f) if r.get("status") == "ready"] results = [] for i in range(0, len(rows), BATCH): batch = rows[i:i + BATCH] window_start = time.time() pending = {submit(row): row for row in batch} print(f"submitted {len(pending)} jobs, {i + len(batch)}/{len(rows)} total") while pending: for job_id in list(pending): time.sleep(POLL_GAP) data = poll(job_id) if data["status"] == "success": row = pending.pop(job_id) safe = row["company"].replace(" ", "_") path = os.path.join(OUT_DIR, f"{safe}_proposal.pptx") download(data["downloadUrl"], path) results.append({**row, "jobId": job_id, "downloadUrl": data["downloadUrl"], "file": path, "status": "done"}) elif data["status"] == "failed": row = pending.pop(job_id) results.append({**row, "jobId": job_id, "downloadUrl": "", "file": "", "status": "failed"}) # Never more than 6 generate calls inside any 60-second window. elapsed = time.time() - window_start if elapsed < 60: time.sleep(60 - elapsed) with open("proposals_out.csv", "w", newline="", encoding="utf-8") as f: w = csv.DictWriter(f, fieldnames=list(results[0].keys())) w.writeheader() w.writerows(results) if __name__ == "__main__": main()

Timing: each batch of 6 takes roughly 60 to 120 seconds including polling, so 200 proposals (34 batches) finish in about 70 minutes on one key. The script is idempotent by status, so if it dies at row 140 you rerun it and only the remaining 60 ready rows are submitted. For larger runs and more queueing patterns, see how to batch-generate presentations with an AI API.

Step 6: Hand reps an editable PPTX, not a PDF

The output of generate is a native PowerPoint file: real text boxes, real tables, the template's master slides. This is the reason to use Fast PPT for proposals rather than an image-rendered deck.

Reps change proposals after they are generated. A discount gets approved, a seat count changes on the call, legal wants a clause on the terms slide, the prospect's logo needs to go on the cover. With an editable PPTX the rep opens the file, edits the pricing slide, and sends it. With a PDF or a deck of rendered images, every one of those changes means regenerating and re-reviewing the whole deck.

The Gmail step (or your CRM's email tool) should therefore send the PPTX itself, not a viewer link, and the subject line should carry the prospect name so reps can find it later. If you also want a polished leave-behind, reps can export to PDF from PowerPoint after their edits.

What a strong proposal deck looks like

Before writing the prompt structure in Step 3, it helps to look at real decks. The 2Slides Gallery hosts reference decks you can study for structure, or pass as referenceImageUrl to the create-like-this endpoint if you go the image-designed route. These are reference decks, not output from the API calls above.

Perplexity ads pitch deck from the 2Slides Gallery, a reference for proposal structure
Figure 4: Cover slide of a pitch deck in the 2Slides Gallery. A proposal cover should name the prospect, the contact, and the date.

Perplexity ads pitch deck from the 2Slides Gallery, a reference for proposal structure
Figure 5: A later slide from the same Gallery deck. Note the single-idea-per-slide density; mirror this in your prompt with numbered sections.

Three patterns from strong proposals carry over to your prompt: open with the prospect's situation in their words (the pain_points column), map each solution point to one pain point, and isolate pricing on a single slide. For a deeper look at what goes in each section, see how to create a sales enablement deck with AI.

What it costs

Fast PPT is billed at 10 credits per page, and a typical proposal runs 10 pages. Theme search and job polling are free.

VolumePagesCreditsCredit packsUSDPer proposal
20 proposals/month2002,0001 × $5 (2,000)$5.00$0.25
500 proposals/month5,00050,0001 × $80 (40,000) + 1 × $22.50 (10,000)$102.50$0.21

The 500 free credits on signup cover 5 full proposals for testing. Credits are pay-as-you-go with no seat requirement, so one shared key can serve a 12-person sales team.

Fast PPT or image-designed slides for this use case?

NeedUseWhy
Editable proposals reps will modifygenerate (Fast PPT), 10 credits/pageNative PPTX, ~30 s, consistent template
Visually bespoke decks for a handful of enterprise accountscreate-pdf-slides, 10 + 100 credits/page at 2KImage-designed layouts from a style prompt
Match an existing brand deck's lookcreate-like-this with referenceImageUrl, same costDerives the design system from a reference image
Narrated proposal video for async sellingcreate-pdf-slides or create-like-this, then generate-narration at 210 credits/pageFast PPT jobs cannot be narrated

For the sheet-driven volume flow in this article, Fast PPT is the right default. Reserve the image pipeline for the 5% of deals where design is the differentiator.

Common pitfalls

  • Bursting past 6 requests per minute. Twenty rows marked ready at once means 20 POSTs in seconds. Queue them: the Python script batches 6 and waits out the 60-second window; in Zapier, rely on replay or stagger with a Delay step.
  • API key in the sheet or a client-side script. Keep the key in the Zapier webhook header or a server-side environment variable. Anyone with the sheet would otherwise spend your credits.
  • Language codes instead of names. responseLanguage wants "Spanish" or "Portuguese", not "es" or "pt-BR". Validate the column with a dropdown in Sheets.
  • Expecting narration or MP4 from Fast PPT. The generate-narration endpoint only accepts jobs from create-pdf-slides or create-like-this. Proposals from generate are PPTX only.
  • Dumping a 4,000-word discovery call transcript into userInput. Summarize first. Three semicolon-separated pain points outperform a wall of notes, and the pricing slide stays clean.
  • Mapping the wrong response field. The job ID lives at data.jobId, not at the top level. The same envelope { success, data: { ... } } applies to the poll response.

Frequently Asked Questions

Can I generate sales decks from CRM data without Google Sheets?

Yes. The API only needs a userInput string and a themeId, so any source works: a HubSpot workflow, a Salesforce export, or a Postgres query. Google Sheets is convenient because reps can edit rows and mark them ready without engineering help. For a CRM-native trigger on deal stage changes, see the HubSpot pattern linked earlier in this article.

How long does one proposal take to generate?

About 30 seconds for a Fast PPT deck once the job starts. In Zapier, a single 60-second Delay before polling is enough in nearly every case. In the Python batch, six proposals submit within a minute and typically all complete within the next minute, so 200 proposals finish in roughly 70 minutes on a single API key.

Will every proposal look the same?

The design will, which is intended. Because every job uses the same themeId, colors, fonts, and layouts are identical across the batch, and only the content changes per prospect. If you want two looks, say one for enterprise and one for SMB, add a theme column to the sheet and map it to two different themeId values.

Do other presentation APIs support this kind of automation?

Yes. Gamma's API at developers.gamma.app offers similar generation automation, though API keys there require a Pro plan. 2Slides API keys work on any account, including a free one with 500 credits, and the Fast PPT output is a native PPTX rather than a hosted page, which is what makes rep-side edits to pricing slides practical.

Can reps edit the pricing slide after generation?

Yes, that is the main reason to use the Fast PPT path. The file is a standard PPTX with editable text boxes and tables, so a rep can change seat counts, apply an approved discount, or add a legal clause in PowerPoint or Google Slides. No regeneration is needed, and the rest of the deck stays untouched.

Next steps

Personalized sales proposals at scale come down to three parts: a clean sheet, one fixed template, and a queue that respects 6 requests per minute.

About 2Slides

Create stunning AI-powered presentations in seconds. Transform your ideas into professional slides with 2slides AI Agent.

Try For Free