Skip to main content

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:

NamespaceForExamples
company.*The issuing entitycompany.legal_name, company.state_of_incorporation
holder.*The counterparty / investorholder.name, holder.address, holder.email
safe.*SAFE termssafe.purchase_amount, safe.valuation_cap, safe.discount_rate
note.*Convertible note termsnote.principal, note.interest_rate, note.maturity_date
option.*Option grantsoption.shares, option.strike_price, option.grant_date
vesting.*Vesting schedulesvesting.start_date, vesting.total_months, vesting.cliff_months
deal.*Round / deal termsdeal.pre_money_valuation, deal.price_per_share, deal.close_date
line_items.*Repeating line itemsline_items.description, line_items.amount
custom.*Anything specific to your productcustom.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:

GET /v1/templates/{id}/sample-payload
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 }
]
}
Node
const sample = await client.templates.getSamplePayload("tpl_yc_safe");
// edit sample.variables, then send (below)
Python
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 422 with a variable_errors list).
  • Types are enforced — a currency/number must be numeric, a date must be ISO 8601.
  • maxLength + overflow — a variable set to overflow_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

Node
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
Python
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 }
]
}