Who it’s for
Venture funds
Accelerators
Awards programmes
Corporate development
How it works
Entrant picks a PDF
Widget mints a one-shot upload token
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.Widget uploads the PDF to Caplia
POST /v1/uploads/decks with the token. Caplia queues the deck for scoring (CRI quality score + thesis-fit) and returns a job_id.The job ID flows on to your CRM
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.Onboarding
Before pasting the widget, get aform_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.
The snippet
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.
Configuration
Three data attributes on the root<div>:
| Attribute | Required | Description |
|---|---|---|
data-form-id | Yes | 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. |
Fields the widget writes
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. |
Webflow setup
Add an Embed element to your form
Paste the widget
REPLACE_WITH_FORM_ID with the form_id Caplia gave you.Set the application-id field name
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.)Add hidden form fields for the widget's outputs
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.Map the new fields in HubSpot
caplia_job_id is the important one — store it as a contact (or deal) property so you can cross-reference back to Caplia later.Endpoints used
The widget calls two endpoints. Both are public — no API key required from the browser.POST /v1/uploads/sign
Issues a short-lived cap_upload_<jwt> token bound to one upload. The form_id must be allowlisted server-side.
POST /v1/uploads/decks
Accepts the PDF using the upload token.
Pulling scoring data back into your CRM
Once you have thejob_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.
/v1/jobs/{job_id} every few minutes and writes scoring data back onto the contact is the simplest production pattern.
Security model
Why isn't there an API key in the widget?
Why isn't there an API key in the widget?
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?
What stops random sites from spamming uploads?
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?
Where does the deck land?
GDPR / data subject rights
GDPR / data subject rights
Limitations
- 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 toXMLHttpRequest. 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.
Need help?
- Onboarding &
form_idprovisioning: paul@caplia.ai - REST API docs: API Reference
- Authentication: Authentication