Variable catalog & signing a SAFE
Permissio templates carry typed variables — the merge fields that get
substituted into the document at send time ({{safe.valuation_cap}},
{{company.legal_name}}, …). This guide covers the recommended
financial-instrument catalog, how to discover a template's variables, how
values are validated, and a full "sign a SAFE in a few calls" walkthrough.
The catalog: namespaces
Variable names are single keys, but the convention groups them by a dotted namespace so a cap-table platform, equity tool, or investor portal can map its own data model cleanly onto a Permissio template:
| Namespace | For | Examples |
|---|---|---|
company.* | The issuing entity | company.legal_name, company.state_of_incorporation |
holder.* | The counterparty / investor | holder.name, holder.address, holder.email |
safe.* | SAFE terms | safe.purchase_amount, safe.valuation_cap, safe.discount_rate |
note.* | Convertible note terms | note.principal, note.interest_rate, note.maturity_date |
option.* | Option grants | option.shares, option.strike_price, option.grant_date |
vesting.* | Vesting schedules | vesting.start_date, vesting.total_months, vesting.cliff_months |
deal.* | Round / deal terms | deal.pre_money_valuation, deal.price_per_share, deal.close_date |
line_items.* | Repeating line items | line_items.description, line_items.amount |
custom.* | Anything specific to your product | custom.internal_ref |
system.* | Reserved for future server-resolved values | — |
The dot is lexical — safe.valuation_cap is one variable key, not a nested
object. You declare variables on the template (with a type of string,
number, date, or currency) and supply their values when you send.
1. Discover a template's variables
Rather than guess, ask the API for a ready-to-send sample payload — one example value per declared variable, plus the manifest:
curl https://api.permissio.us/v1/templates/tpl_yc_safe/sample-payload \
-H "Authorization: Bearer $PERMISSIO_KEY"
{
"template_id": "tpl_yc_safe",
"variables": {
"company.legal_name": "Acme, Inc.",
"company.state_of_incorporation": "Delaware",
"holder.name": "Jane Investor",
"safe.purchase_amount": "$1,000,000",
"safe.valuation_cap": "$1,000,000",
"safe.discount_rate": 20,
"deal.close_date": "2026-01-15"
},
"variable_manifest": [
{ "name": "company.legal_name", "type": "string", "required": true, "description": null },
{ "name": "safe.valuation_cap", "type": "currency", "required": true, "description": null }
]
}
const sample = await client.templates.getSamplePayload("tpl_yc_safe");
// edit sample.variables, then send (below)
sample = client.templates.get_sample_payload("tpl_yc_safe")
# edit sample["variables"], then send (below)
2. Validation & lock-at-send
When you create an envelope, submitted variables are validated against the
template's schema — the same rules on the API as in the dashboard:
- Required variables must be present (else
422with avariable_errorslist). - Types are enforced — a
currency/numbermust be numeric, adatemust be ISO 8601. maxLength+ overflow — a variable set tooverflow_behavior: "reject"fails the request if too long;"truncate"crops it and records a render warning.
Values are locked at send: whatever you submit is frozen onto the envelope and rendered into the PDF. They are never re-materialized afterward, so the signed document and its audit certificate always reflect exactly what was sent.
A validation failure looks like:
{
"error": "Variable validation failed: Required variable \"company.legal_name\" is missing",
"code": "validation_error",
"details": { "variable_errors": [ { "variable": "company.legal_name", "message": "Required variable \"company.legal_name\" is missing" } ] }
}
3. Sign a SAFE in a few calls
import { Permissio } from "permissio-sdk";
const client = new Permissio({ apiKey: process.env.PERMISSIO_KEY! });
// 1. See what the SAFE template expects
const sample = await client.templates.getSamplePayload("tpl_yc_safe");
// 2. Create + send in one call with your real values
const envelope = await client.envelopes.create({
template_id: "tpl_yc_safe",
title: "YC SAFE — Acme × Jane Investor",
send_immediately: true,
signers: [
{ role_name: "Company", email: "founder@acme.com", name: "Sam Founder" },
{ role_name: "Investor", email: "jane@invest.com", name: "Jane Investor" },
],
variables: {
...sample.variables,
"company.legal_name": "Acme, Inc.",
"holder.name": "Jane Investor",
"safe.purchase_amount": "$500,000",
"safe.valuation_cap": "$10,000,000",
"safe.discount_rate": 20,
"deal.close_date": "2026-02-01",
},
});
console.log(envelope.signing_links); // per-recipient URLs to embed or send
from permissio import Permissio
client = Permissio(api_key=os.environ["PERMISSIO_KEY"])
sample = client.templates.get_sample_payload("tpl_yc_safe")
envelope = client.envelopes.create(
template_id="tpl_yc_safe",
title="YC SAFE — Acme × Jane Investor",
send_immediately=True,
signers=[
{"role_name": "Company", "email": "founder@acme.com", "name": "Sam Founder"},
{"role_name": "Investor", "email": "jane@invest.com", "name": "Jane Investor"},
],
variables={
**sample["variables"],
"company.legal_name": "Acme, Inc.",
"safe.valuation_cap": "$10,000,000",
"safe.discount_rate": 20,
"deal.close_date": "2026-02-01",
},
)
To send the same SAFE to many investors at once, use
bulk send — one row per investor, each with its own
holder.* and safe.* values.
Declaring variables on a template
Variables are declared when you create a template (or auto-detected from
{{placeholder}} tokens in the document). Each declares a name, type, and
whether it's required:
{
"name": "SAFE Template",
"document_url": "...",
"variables": [
{ "name": "company.legal_name", "type": "string", "required": true },
{ "name": "safe.valuation_cap", "type": "currency", "required": true },
{ "name": "safe.discount_rate", "type": "number", "required": false },
{ "name": "deal.close_date", "type": "date", "required": true }
]
}