Issue securities on a Carta cap table. Use when the user asks to issue certificates, stock certificates, option grants (ISO, NSO, EMI, CSOP, Unapproved, Startup Concessions, Non-Concessional, ZEPO), profits interest units (PIUs), to draft shares, grants or units, or to resume issuing from a draft set. USE WHEN the user says "issue", "grant", "draft", "award", "give equity", "give shares", "give stock", "create a certificate", "create a grant", "issue a profits interest", "issue PIUs", "grant profits interest units", "set up an option grant", "issue equity to a named person", or names any specific security type above. Also USE WHEN the user points at a spreadsheet, CSV, Carta import template, or a grant/award document as the source of the issuance ("issue the grants in this file", "here's our import template"). Also USE WHEN asked for help issuing with nothing attached ("help me issue options in Carta", "can I issue grant awards?").
carta-cap-table:6.91.7
From raw input to issued securities on a Carta cap table. These three types, and no others:
| Type | Example prompt |
|---|---|
| Certificate | "1 cert for Jane Doe, 1000 Series A at $1.50." |
| Option grant | "1000 ISOs to Jane at $1.50 on the 2024 Plan." |
| Profits interest unit | "5,000 CC units to Jane, $2.00 per-unit threshold." |
A spreadsheet or award document sources those same three types; "resume draft set 472"
re-enters a saved one. To fix an already-issued security, use carta-modify-issuables.
Out of scope — route to the Drafts UI for RSUs, SARs, CBUs, warrants, convertibles, SAFEs, convertible debt, and anything custom: a legend, schedule or exercise period the corporation has no template for. Picking one it does have is in scope, and a grant's vesting schedule is the normal case, not a deflection.
"This skill issues certificates, option grants and profits interest units today. For <thing>, use the Drafts UI in the Carta app."
The artifact path needs no Carta tool of your own. The page finds the connector at runtime, resolves the company, fetches its own reference data, and shows a no-connection fix. So never assess, mention, or report Carta's connectivity on that path — no check, caveat, or status line. Build the page.
Your first tool call is the build's Bash, with no text before it — unless the request
names an environment your session can't confirm, the one case
below that calls get_current_user first,
or states nothing to issue.
Two things read like a verdict and carry none:
mcp__carta__authenticate-shaped name in your tool list or a "needs
authentication" list. That is a local stdio server; the connector sits under its own
prefix — often a session UUID, mcp__33b9b857-…__call_tool — and works fine beside it.carta server, and never applies here.Neither is an input to anything: don't weigh, mention, or act on them.
A 5xx, a gateway error, or an HTML body from a Carta call is transient — retry exactly once, then report a temporary problem. Never an auth verdict, and an HTML response to a JSON call is never content to parse.
The chat surface does load Carta tools, and engine.md § Step 1 is the only place this skill concludes anything about the connection.
corporation_id isn't unique across environments, so aiming at the wrong one issues real
securities onto the wrong company. When the request implies an environment that differs from
the connected one — a host, a Carta link, "sandbox", "demo", "production" — hard stop and
ask. Being the only connected server isn't the same as being the right one.
The connected Carta is the connector whose tools you have — call_tool, welcome and
list_accounts under one prefix. A connector offering only authenticate isn't connected;
ignore it. Its environment comes from your session — its ## claude.ai <name> heading, its
Default connector name line, or a readable prefix (mcp__claude_ai_Carta_Demo__… → demo) —
never from an example in this skill. First match wins:
get_current_user once (load it with ToolSearch if deferred) and compare its
environment, or its base_url host when environment reads unknown. Never guess and
never build on an unknown: the page resolves the company by name, so the same name in the
wrong environment is a real company there.A request that states nothing to issue — no person, quantity or term, no file and no pointer to one ("help me issue options in Carta", "can I issue grant awards?") — starts here, on every surface: read references/source-question.md and ask its one question before anything else. Anything stated builds straight away.
Take the first row that matches. Every row reads your own tool list — never the disk, never an env var.
| Condition | Surface |
|---|---|
| the user asked for a different surface than the one you would pick | the one they asked for |
the Artifact tool is present | artifact — § The artifact surface |
| else | chat — references/chat-surface.md |
Record the selection once. Never re-detect.
The artifact is the form, and its whole path is in this file: Skill → Bash → Artifact,
nothing further to read. The page collects, saves and validates on its own, its HTML
nowhere near your context. Chat is for a host without Artifact, and is the only path
that spends your turns on data entry: every term costs a round trip through you.
Never render a form with show_widget or preview_start, whatever else is missing. A
widget is for one small question or short status; an issuance form in one can't be
submitted on some hosts
(incidents.md).
security_typeResolve once, at the top. Pass on every draft-set tool call.
| Cue | security_type |
|---|---|
| "cert", "certificate", "shares", "Series A", "common", "membership units" | certificate (default) |
| "option", "ISO", "NSO", "grant" (with plan), "EMI", "CSOP", "Unapproved", "Startup Concessions", "ESS", "Non-Concessional", "ZEPO" | option_grant |
| "PIU", "PIUs", "profits interest", "profits interest unit", "incentive units", "threshold", "hurdle" | piu |
| Ambiguous ("equity") | Ask with AskUserQuestion |
| Mixed in one prompt | Ask which to run first; run the others in follow-ups |
| Out-of-scope security | Route to the Drafts UI; stop |
"units" and "membership units" aren't PIU cues. On an LLC, Carta's equity language
renames a certificate to a membership unit, so bare "units" lands at certificate at
least as often as at piu. Read it as piu only alongside a real PIU signal —
"profits", "incentive", a threshold or hurdle amount, or a named equity plan. Without one
it's a fork → AskUserQuestion, never a silent pick.
A bare "N <securities>" is a quantity, not a headcount. "100 option grants", nobody named, no plural-person language → one recipient getting 100; only people-language ("100 employees", "100 new hires") makes N a row count. Never open a form with 100 blank rows, and never ask who the recipients are first — a missing recipient is an empty field on the form, not a chat question.
Budget: two turns to a form on screen — Bash, then Artifact with
the company check beside it, plus the environment
check only when it applies.
The page does the work: it resolves the company, the connector and the named people against the cap table, fetches its own reference data, collects the terms, saves and validates the draft set, shows the review, and issues on confirmation. None of it reaches your context — not the roster, field manifest, or HTML. You run one script and publish it.
--company-name;
the page resolves it.call_tool: ToolSearch it in the same message as the Bash. Unloaded,
it's refused for a missing top-level _instrumentation_v2.rows become the seed's. An attached document is already in context:
seed from it, no Read.Nothing else. Don't fetch reference data, the roster, plans, valuations, share classes or the field manifest — the page fetches what it needs, so yours is a wasted round trip.
One Bash call: mkdir, seed heredoc and build together, no Write. The seed is optional
and small: only what the prompt supplied, so the page can prefill it.
Set SKILL to the absolute path
§ Where everything else lives resolves for you — paste it.
The plugin-root variable isn't exported into the Bash tool's shell, so a command that still
carries it unresolved fails on a path starting /skills/ — nothing else in the command is
wrong when that happens.
SKILL=<paste the resolved absolute path>
WORK=<your scratchpad dir>
mkdir -p "$WORK"
cat > "$WORK/_seed.json" <<'JSON'
{"stakeholders": ["Tagg Palmer"], "quantity": "100"}
JSON
uv run "$SKILL/issuance-artifact/scripts/build_artifact.py" \
--company-name "IMIM" \
--security-type option_grant \
--seed "$WORK/_seed.json" \
--out "$WORK/issuance-imim-option_grant.html"--corporation-id. The page resolves the name. Add --corporation-id <n> only
when you already hold a bare numeric id — from the fallback lookup, or from a resume.--seed takes a path, never an inline blob. Omit it entirely when the prompt named
nobody and no terms; the page then opens with one blank recipient row.stakeholders (names verbatim), quantity, issue_date, terms, source,
and rows from the import sub-skill. An unknown key fails the build.terms carries every term the document or prompt states, in its words; the page
matches names to Carta's lists and asks only for what matches nothing:
{"option_plan": "<plan name>", "grant_type": "<ISO|NSO|…>", "exercise_price": "<price>", "board_approval_date": "<YYYY-MM-DD>", "vesting": {"text": "<the schedule's words>", "months": <total>, "cliff_months": <cliff>}, "term_years": <years>}.
Also vesting_start_date, grant_expiration_date, early_exercise, share_class,
price_per_share, threshold_value. Omit unstated ones.{"stakeholders": [{"name": "Tagg Palmer", "quantity": 100}, {"name": "Emily Wilson", "quantity": 50}]}.
Top-level quantity covers everyone else. A percentage stays one: 2.75% is "2.75".terms, only grant_type,
exercise_price, price_per_share, threshold_value, board_approval_date:
{"name": "Emil Vaselvee", "quantity": 1250, "terms": {"grant_type": "ISO"}}. Rows
that disagree with no batch value show Multiple on the shared field — expected.source is {"name": "<file name>", "url": "<link>"} for the Source tile; omit it
when nothing was attached. url builds only as https or a /_blob/ asset (§ 3).draft_set_id — without it the page mints a second
draft set of the same rows (hard rule 3). The page reads the set's rows and
terms back itself; seed load_drafts rows, each with its draft_pk, only as its fallback
(resume-flow.md). Never relay terms.--out is a stable path for this company and type — a lowercase company slug plus
the type, as above.Never read the built file back — ~135KB kept out of context.
Artifact({
file_path: "<the --out path>",
description: "Collect and review the option grants before issuing them.",
icon: "grant", // "certificate" | "grant" | "units", per security_type
capabilities: {
mcp: { servers: [{ server: "<the connector's display name — see below>", tools: ["call_tool"] }] },
db: {}
}
})server is the display name of
the connected Carta — never one that
only offers authenticate, never a name from this file. Take the first your MCP
instructions give:
## claude.ai <name> → <name>, copied exactly.Default connector name: "…" line in the block headed by your tool-name
segment — between mcp__ and the next __, so
mcp__33b9b857-8443-4b2d-b191-2d9b6c50eb86__call_tool → 33b9b857-8443-4b2d-b191-2d9b6c50eb86.Never guess a name from a readable prefix. The publish never checks it — right or wrong reads the same — so if the user says the page can't see Carta after a step 2 name, their connector was renamed: republish with step 3.
url; skip action: "list". The same file path redeploys to the same URL.icon goes on the first publish only; omit on redeploy.capabilities object on any redeploy that passes it: a non-empty
object replaces the stored grant, so an omitted capability is revoked. Omit the field
entirely to carry the grant forward — the cheaper redeploy.tools at that one: every Carta command goes through the call_tool proxy.assets: {} to the first publish, then
upload_asset the file, rebuild with source.url set to the returned url, and
republish with capabilities omitted.Read the publish result's warnings. One matters: an unresolved connector name means the grant isn't wired and every card comes up empty — that sends this run to the chat surface instead. Any other warning is informational.
Say one short line, then the URL on its own line as bare text:
The option-grant form is open — set the terms once, add recipients, and hit Review. It'll flag anything Carta needs before you can issue.
https://claude.ai/code/artifact/58fa48f8-693e-43e3-8491-018976b769d6
Never a markdown link. On a host that opens the form in a side panel it renders as the title alone — if that panel failed, the address is the one thing the user can't see or copy. Bare URL every time, panel or not.
Echo nothing else — no ids, field names, or summary of what you prefilled (hard rule 8). A hook's footer still goes last. The first open asks the viewer to allow the Carta connection; until then the page shows its no-connection state and says what to do.
Parallel with the Artifact call (skip it with --corporation-id): call_tool
cap_table:get:resolve_company {"name": "<--company-name>"}.
resolved / unavailable → nothing. No rebuild — the page already resolves the name.ambiguous / suggestions → AskUserQuestion "Which company did you mean?", one option
per candidates name (max 4; twins get Carta ID <corporation_id> as description).
Never pick one yourself, even a lone suggestion.not_found → ask for the name as Carta shows it; re-check.Only after the user answers one of those: rebuild with the same flags plus
--corporation-id (--company-name stays required) to the same --out, and republish
(no icon).
This surface performs the irreversible write itself. The page saves, validates, shows the review, and asks for one confirmation in its own sheet — that click is the gate, and the write goes out under the viewer's connector grant. A hand-off document can't wake this session, so a page stopping at a saved draft set could never issue.
After the write the page seals itself — no further save or issue — whenever the outcome is settled or unknowable: issued, a timeout, or accepted-and-nothing-reported. A refused value and a duplicate stakeholder both leave it usable, since neither wrote anything.
So there's nothing to do after § 3. End your turn on that one line. Don't read the store yet, poll, narrate, or re-publish: filling an issuance form is minutes of human work.
At the start of your next turn, whatever the user typed, read what the page recorded:
Artifact({action: "read_db", url: "<the URL the publish returned>",
db_op: "get", collection: "issuance", doc_id: "handoff"}){ "status": "issued", "issued": 1, "draft_set_id": 472, "security_type": "option_grant",
"holders": ["Tagg Palmer"], "totals": {"USD": {"quantity": 100, "value": null}},
"issue_date": "2026-09-21" }Branch on status, never summary. holders, totals and issue_date are for the
closing line:
status | What it means | What you do |
|---|---|---|
| not found | not finished. The normal state | Answer whatever they asked. Not an error, never reported as one |
issued | the page is done. pending_board counts option grants held for a board consent: drafts, not issued | Close per issue-and-close.md § On success: signatories were notified to sign; once they sign, securities go to stakeholders. With pending_board, add that those grants await board approval and the page links to the consent in Carta. Never call issue_securities or send a consent — that issues twice |
draft | saved, not validated, not issued | Say the draft is saved and they can return to it, and stop. A saved draft isn't an approval to issue |
needs_claude | the page tried and couldn't finish | Read reason: duplicates → mutate-recovery.md § Duplicate resolution; unknown_outcome → read the set's state before any write, rows may already be issued; partly_issued → some rows are issued: read the set's state, never issue it again; rows_removed → the named rows were deleted from their document set and not saved back: have the user retry or re-save them; nothing_issued → mutate-recovery.md |
| What you see | What it means | What you do |
|---|---|---|
| Publish warns it couldn't resolve the connector | the page has no Carta access | chat surface |
| User says the page can't find the company | the name matched none or several | Check the company |
| User says the page is empty, or every section couldn't load | the viewer hasn't allowed the connector, or Carta is down for them | Tell them to allow the Carta connection when asked, or reconnect Carta in Settings → Connectors. Re-publishing doesn't help — unless server came from step 2 of § 3 |
| User sees a hard stop in the page | the account isn't set up for this issuance | Read it back in plain language and stop. The fix is in Carta, not here |
| User reports validation errors they can't clear | the server refused a value | Those belong to the page, shown against its own fields. Only if the page can't act on a message — a fund-structure block, a duplicate stakeholder, a missing FMV — read mutate-recovery.md |
| User says it issued but no document appears | the store write failed after the write landed | Don't issue. The page names the draft set on screen; ask for it and read its state |
| User wants to change a term after confirming | — | Tell them to hit Back in the page's sheet and confirm again. Don't rebuild rows yourself — the draft set is the record |
AskUserQuestion on the open page for anything the page collects. It's
unrestricted for a genuine fork the page can't present — two Carta environments, a mixed
security type — and for recovery after a server rejection.The chat surface ends at a saved, validated draft_set_id, and you perform the
irreversible write so the host's own confirmation prompt fires. The artifact surface
issues from the page instead — its Confirm sheet is that gate, so you report the result
and never re-issue (§ 4).
mcp__carta__call_tool({"name": "cap_table__mutate__issue_securities", "arguments": {
"corporation_id": <corporation_id>, "security_type": "<certificate|option_grant|piu>",
"draft_set_id": <draft_set_id>}})call_tool takes name + arguments; the wire name carries double underscores —
cap_table:mutate:issue_securities is the prose form, never the argument.
No drafts key — the draft set already holds the rows the user approved
(hard rule 6).
Don't call validate_drafts again first. The surface already validated, and
issue_securities re-validates server-side before writing.
Then read references/issue-and-close.md for the response branches, what to say per holder, and the closing lines. On a rejection a re-call can't clear, read references/mutate-recovery.md.
Every surface.
AskUserQuestion on the chat surface — never
stacked on an open page (§ What not to do on this path).
Recovery questions after a server short-circuit are unrestricted.draft_set_id from the
first mutate on every later issue_securities, save_drafts, load_drafts,
validate_drafts, resolve_duplicate_stakeholder: omit it and the server mints a
second draft set of the same incomplete rows. Put each row's draft_pk from its first
save on every retry, plus every required field: omit it and the row inserts instead of
updating. A timeout isn't an error — the call may have already succeeded, so
retrying with wrong params risks a duplicate set or double-issue; read
§ Timeouts & retries first.draft_set_id and no drafts key. Re-sent rows are only
probably identical to the reviewed ones — one transposed digit issues terms nobody
approved, and no later gate compares the two. Resend rows only to change them, each with
its draft_pk attached.type="PIU", so that path looks like a fallback when a PIU call is refused. It
isn't — it issues a plain unit certificate with no threshold value, a different security.(<number>)
after a name. Humanize payload keys before surfacing them, banner_errors included:
_ → space, Title Case, exceptions in labels.md.Every rule here comes from a real run that went wrong (incidents.md).
Every path below starts ${CLAUDE_PLUGIN_ROOT}/skills/carta-issuance/ — that variable
anchors at the plugin root, so the skill segment belongs in the path. Don't search
for them: on several hosts Glob/find can't reach the plugin mount and return empty.
references/chat-surface.md — the last-resort path: its phases, and what it reads.references/issue-and-close.md — the mutate's response branches and the closing lines.references/mutate-recovery.md — on a server rejection a re-call can't clear.references/resume-flow.md — "resume draft set 472".references/incidents.md — before weakening or arguing with any rule.issuance-import/SKILL.md — the prompt points at a spreadsheet, CSV or award document. On
every surface, before any form opens (§ 1 has the one exception). It owns
parsing; never hand-read a workbook.2f20566
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.