Drop a pitch-deck upload widget into any Webflow, WordPress, or HTML form. The PDF goes straight to Caplia for scoring; everything else in your form keeps flowing to your existing CRM. Built for VCs, accelerators, awards programmes, and corp-dev intakes.
Use this file to discover all available pages before exploring further.
The Caplia Venture Form Widget is a single block of HTML you paste into your existing application form. Entrants pick a PDF, the widget uploads it directly to Caplia, and writes the resulting job ID into a hidden form field that flows on to your CRM (HubSpot, Salesforce, anywhere). No backend changes on your side, no API key in the browser.Built for any public form where the entrant uploads a pitch deck.
Your fund’s public “submit your deck” form. Cold inbound lands in Caplia pre-scored, sorted by CRI quality and thesis fit, ready for partner review.
Accelerators
Cohort application forms. Every submitted deck is scored on arrival; selection committees see a ranked shortlist instead of a Google Drive folder.
Awards programmes
Innovation award entries, pitch competitions, founder prizes. Judges work from CRI scores and per-category thesis fit instead of reading every deck cold.
Corporate development
Partnership intake forms. Inbound startup approaches are scored against your strategic thesis automatically before a BD lead picks them up.
The widget renders a styled “Choose pitch deck” button inside your existing form. It enforces PDF-only, 50 MB max, client-side and server-side.
2
Widget mints a one-shot upload token
A POST /v1/uploads/sign call returns a cap_upload_<jwt> token — 5-minute TTL, single-use, signed server-side. No long-lived API key in the browser.
3
Widget uploads the PDF to Caplia
The PDF posts to POST /v1/uploads/decks with the token. Caplia queues the deck for scoring (CRI quality score + thesis-fit) and returns a job_id.
4
The job ID flows on to your CRM
The widget writes the job_id into a hidden input named caplia_job_id. When the entrant submits the rest of the form, that ID lands in HubSpot (or wherever) alongside their other fields.
5
Scoring lands in your Caplia dashboard
The deck is processed asynchronously. Once scored, the entry appears in your Caplia pipeline with CRI score, per-thesis fit, and is queryable by the application ID you supplied.
Before pasting the widget, get a form_id from your Caplia contact (Connor, or paul@caplia.ai). It’s an opaque string like acme-2026 or fund-x-inbound that maps to your team and the user submissions land under. We allowlist it server-side in the same step.
Paste this whole block into a single Webflow Embed element (or any HTML container that allows inline <script>) placed inside your application form, where your “deck URL” field used to be.
<!-- Caplia Venture Form Widget v1 --><div data-caplia-upload data-form-id="REPLACE_WITH_FORM_ID" data-application-id-field="application_number" data-api-base="https://api.venture.caplia.ai"> <input type="file" accept=".pdf,application/pdf" hidden> <button type="button" class="caplia-pick">Choose pitch deck (PDF, up to 50 MB)</button> <div class="caplia-status" role="status" aria-live="polite"></div> <input type="hidden" name="caplia_job_id" value=""> <input type="hidden" name="caplia_deck_filename" value=""></div><style> [data-caplia-upload] { font-family: inherit; color: inherit; } [data-caplia-upload] .caplia-pick { display: block; width: 100%; padding: 0.875rem 1rem; border: 1px dashed currentColor; border-radius: 0.375rem; background: transparent; color: inherit; font: inherit; cursor: pointer; text-align: center; } [data-caplia-upload] .caplia-pick:hover { background: rgba(0,0,0,0.04); } [data-caplia-upload].is-uploading .caplia-pick { opacity: 0.6; pointer-events: none; } [data-caplia-upload] .caplia-status { margin-top: 0.5rem; font-size: 0.875rem; min-height: 1.25rem; } [data-caplia-upload].is-error .caplia-status { color: #b91c1c; } [data-caplia-upload].is-success .caplia-status { color: #15803d; }</style><script>(function () { document.querySelectorAll('[data-caplia-upload]:not([data-caplia-init])').forEach(function (root) { root.setAttribute('data-caplia-init','true'); var formId=root.dataset.formId, appIdField=root.dataset.applicationIdField||'', apiBase=(root.dataset.apiBase||'https://api.venture.caplia.ai').replace(/\/+$/, ''); var fileInput=root.querySelector('input[type="file"]'), pickBtn=root.querySelector('.caplia-pick'), statusEl=root.querySelector('.caplia-status'), jobIdInput=root.querySelector('input[name="caplia_job_id"]'), filenameInput=root.querySelector('input[name="caplia_deck_filename"]'), form=root.closest('form'); if (!formId || formId==='REPLACE_WITH_FORM_ID') { statusEl.textContent='Widget not configured: missing form id.'; root.classList.add('is-error'); return; } function setStatus(m,k){root.classList.remove('is-uploading','is-error','is-success');if(k)root.classList.add('is-'+k);statusEl.textContent=m||'';} function getApplicationId(){if(!appIdField||!form)return;var el=form.querySelector('[name="'+appIdField+'"]');var v=el&&(el.value||'').trim();return v||undefined;} pickBtn.addEventListener('click',function(){fileInput.click();}); fileInput.addEventListener('change', async function(){ var file=fileInput.files&&fileInput.files[0]; if(!file)return; if(file.type&&file.type!=='application/pdf'){setStatus('Please choose a PDF file.','error');return;} if(file.size===0){setStatus('That file appears to be empty.','error');return;} if(file.size>50*1024*1024){setStatus('File is larger than 50 MB.','error');return;} setStatus('Uploading '+file.name+'…','uploading'); jobIdInput.value=''; filenameInput.value=''; try{ var signRes=await fetch(apiBase+'/v1/uploads/sign',{method:'POST',headers:{'Content-Type':'application/json'}, body:JSON.stringify({form_id:formId,application_id:getApplicationId()})}); if(!signRes.ok){var e=await signRes.json().catch(function(){return{};});throw new Error((e.error&&e.error.message)||'Could not start upload ('+signRes.status+').');} var sign=await signRes.json(); var fd=new FormData(); fd.append('file',file); var up=await fetch(apiBase+'/v1/uploads/decks',{method:'POST',headers:{'Authorization':'Bearer '+sign.token},body:fd}); if(!up.ok){var e2=await up.json().catch(function(){return{};});throw new Error((e2.error&&e2.error.message)||'Upload failed ('+up.status+').');} var data=await up.json(); jobIdInput.value=data.job_id; filenameInput.value=file.name; pickBtn.textContent='Replace pitch deck'; setStatus('Uploaded '+file.name+' ✓','success'); }catch(e){console.error('caplia upload error',e);setStatus(e.message||'Upload failed. Please try again.','error');} }); if(form){form.addEventListener('submit',function(e){if(root.classList.contains('is-uploading')){e.preventDefault();setStatus('Please wait for the pitch deck upload to finish.','error');}});} });})();</script>
Opaque identifier issued by Caplia onboarding (e.g. acme-2026, fund-x-inbound). Must match the entry we allowlisted server-side, or the widget will refuse to upload.
data-application-id-field
No
The name attribute of an existing form field that holds the entrant’s application number / candidate ID. The widget reads its value at upload time so the Caplia entry can be matched back to your CRM row. Omit it if you don’t have one.
data-api-base
No
API origin override. Defaults to https://api.venture.caplia.ai (production). Set to https://api-sandbox.venture.caplia.ai during testing.
Make sure your form has two corresponding fields (they can be hidden — the widget creates them if missing in pure HTML, but in Webflow you’ll want to declare them so HubSpot can map them):
Field name
Purpose
caplia_job_id
The Caplia job ID (UUID). Map this to a CRM property like “Caplia Job ID”. Use it later to fetch scoring data via GET /v1/jobs/{id} with your cap_inv_live_* key.
caplia_deck_filename
The entrant’s original PDF filename. Useful for your CRM record.
In the Webflow designer, drop a Code Embed component inside the Form block, where your old “deck URL” field used to be.
2
Paste the widget
Paste the snippet above into the Embed. Replace REPLACE_WITH_FORM_ID with the form_id Caplia gave you.
3
Set the application-id field name
Change data-application-id-field="application_number" to whatever the name of your existing Application Number / Candidate ID field is. (Right-click the field in Webflow → Settings → Name.)
4
Add hidden form fields for the widget's outputs
Add two Form components inside the same form, type “Text”, with names caplia_job_id and caplia_deck_filename. Set them to hidden in the Webflow designer (or via custom CSS). HubSpot will receive their values on form submit.
5
Map the new fields in HubSpot
In your existing Webflow → HubSpot mapping, add the two new fields. caplia_job_id is the important one — store it as a contact (or deal) property so you can cross-reference back to Caplia later.
6
Test on the staging Webflow domain
Set data-api-base="https://api-sandbox.venture.caplia.ai" and use the sandbox form_id we provision for you. Submit a test entry, confirm the caplia_job_id lands in HubSpot, then flip to production by removing the data-api-base override.
Once you have the job_id on the CRM side, your back-office tooling can fetch results with the regular Caplia REST API using a cap_inv_live_* key — see Authentication.
# Poll the job (returns company_id once intake is done)curl https://api.venture.caplia.ai/v1/jobs/a4f6c0d8-... \ -H "Authorization: Bearer cap_inv_live_..."# Then fetch CRI + thesis-fit scorescurl https://api.venture.caplia.ai/v1/companies/{company_id}/scores \ -H "Authorization: Bearer cap_inv_live_..."
A scheduled HubSpot workflow (or Salesforce flow) that pings /v1/jobs/{job_id} every few minutes and writes scoring data back onto the contact is the simplest production pattern.
Long-lived cap_inv_live_* keys must never ship to a browser — anyone could view-source the page and use the key to push decks into your pipeline, or read your data. The two-step token flow (/v1/uploads/sign → cap_upload_<jwt>) is the same pattern AWS uses for S3 presigned POSTs: server-side authorisation, short-lived, single-use, scoped to one operation.
What stops random sites from spamming uploads?
Two gates. First, only form_ids that have been explicitly allowlisted on the Caplia side can mint tokens — random form_id strings return 404. Second, each token has a 5-minute TTL and is bound to a single upload. We add per-IP rate limits and optional Cloudflare Turnstile challenges on top once a programme grows beyond a small expected entrant pool.
Where does the deck land?
Encrypted Caplia storage, scoped to your team. Only members of your Caplia team can access entries. The deck never touches Webflow’s or HubSpot’s storage.
GDPR / data subject rights
Entrants’ decks are processed on the basis of the consent they give in your application form. We provide tooling to bulk-export or bulk-delete a form’s entries when a programme concludes — talk to Connor about retention windows.
PDF only. Other file types are rejected by both the widget (client-side) and the API (server-side).
50 MB maximum. Server-enforced; tokens carry the limit in their signed claims.
No upload progress bar yet. The widget shows “Uploading…” but not byte-level progress; fetch() doesn’t expose that without falling back to XMLHttpRequest. We’ll add a progress bar in v2 if customer feedback warrants it.
One widget instance per form. Multiple instances on the same page are supported (each is initialised independently), but two instances inside the same <form> would clobber each other’s hidden inputs.