| name | commonpaper |
| description | Query and manage contracts via the Common Paper REST API. Use when the user asks about their contracts, agreements, signers, NDAs, CSAs, renewals, deal values, or wants to create/void/reassign/send agreements. Also use when user mentions "Common Paper", "commonpaper", asks contract-related questions like "how many signed contracts do I have?" or "do I have an NDA with X?", wants to create or update agreement templates, wants to onboard or set up their Common Paper account, or asks about uploading attachments. |
Common Paper API Skill
Security Rules
CRITICAL — follow these rules at all times:
- NEVER display, echo, or include the API token in chat output. Do not print credentials in responses, code blocks, or explanations unless the user explicitly asks to see them.
- NEVER pass the API token as a literal string in command-line arguments. Always read it from the credentials file via command substitution (see Making Requests below) so the token value is not visible in the command text shown to the user.
- When showing the user a curl command (e.g., if they ask "what query did you use?"), replace the auth header with a placeholder like
-H "Authorization: Bearer $CP_TOKEN". Never substitute in the real value. Always URL-encode brackets as %5B and %5D in the shown command so it can be copy-pasted into zsh without errors.
- Sanitize user inputs before inserting into URLs. Strip or URL-encode any characters that could break the URL or be used for injection (e.g.,
&, =, #, ?, newlines, backticks, $(), semicolons). Only allow alphanumeric characters, spaces, dots, commas, hyphens, and common punctuation in filter values.
- Validate credential format before saving. The API token should match the pattern
zpka_* and contain only alphanumeric characters and underscores.
Authentication
The API uses Bearer token authentication. Only an API token is required — the organization is inferred from the token. No Organization ID header is needed.
Credential Loading
Before making any API call, check if a saved token exists:
cat ~/.claude/skills/commonpaper/cp-api-token 2>/dev/null
If the file exists and is non-empty, use its contents as the token. Do not echo or display the value to the user.
If the file does not exist or is empty, ask the user:
- API Token: "What is your Common Paper API token? (You can generate one from your account's Integrations tab)"
Credential Validation
Before saving or using the token, validate it:
- Must contain only alphanumeric characters and underscores
After receiving and validating the token, test it with a lightweight API call:
curl -s -H "Authorization: Bearer $(cat ~/.claude/skills/commonpaper/cp-api-token)" "https://api.commonpaper.com/v1/agreements?page%5Bsize%5D=1" -o /dev/null -w "%{http_code}"
If the response is not 200, inform the user that their token appears invalid and ask them to double-check.
Saving Credentials
After successful validation, ask: "Would you like me to save this token for future sessions?"
If yes:
echo -n "THE_TOKEN" > ~/.claude/skills/commonpaper/cp-api-token && chmod 600 ~/.claude/skills/commonpaper/cp-api-token
Making Requests
All API requests MUST read the token from the credentials file via command substitution to avoid exposing it as a literal in the command.
curl -s -H "Authorization: Bearer $(cat ~/.claude/skills/commonpaper/cp-api-token)" \
"https://api.commonpaper.com/v1/agreements"
For POST/PATCH requests, add:
-H "Content-Type: application/json"
Base URL: https://api.commonpaper.com/v1
IMPORTANT — URL encoding: Always URL-encode square brackets in query parameters. Use %5B for [ and %5D for ]. Unencoded brackets will cause zsh: no such file or directory errors.
Note: While $(cat ~/.claude/skills/commonpaper/cp-api-token) is expanded by the shell before execution (so the token briefly appears in the process list), this is significantly better than having the literal token in the command text shown in chat. The token file is also chmod 600 so only the user can read it.
Endpoints
List Agreements (Primary endpoint for queries)
GET /v1/agreements
Paginated — default page size is 15 (override with page[size]).
Compact vs full list responses: By default, the list response omits type-specific nested attributes (such as csa, csa_order_form, dpa, baa, and other agreement-type sub-models). Pass full=true to include those nested attributes in the list response. Use GET /v1/agreements/{id} when you only need one agreement's full details.
GET /v1/agreements?full=true
URL-encoded: full=true (no brackets). Combine with filters and pagination, e.g. filter%5Bstatus_eq%5D=signed&full=true&page%5Bsize%5D=100.
When to use full=true on list:
- You need nested fee/term data from multiple agreements in one pass (e.g.,
csa_order_form.fees, csa.include_covered_claims)
- You're building a report that requires type-specific fields without fetching each agreement individually
When the compact list is enough:
- Counting agreements (
meta.pagination.records)
- Listing counterparties, statuses, dates, and summary fields
- Most search/filter questions about who signed what
Returns JSONAPI format with pagination metadata:
{
"data": [ { "id": "...", "type": "agreement", "attributes": { ... }, "links": { ... } } ],
"meta": { "pagination": { "current": 1, "next": 2, "last": 5, "records": 127 } },
"links": { "self": "...", "next": "...", "last": "..." }
}
Key response fields:
meta.pagination.records — total number of matching agreements (use this for counts instead of paginating through all results)
data[].attributes — agreement fields
data[].attributes.display_status — human-friendly status (e.g., "Completed", "Waiting for counterparty") — prefer this over status when presenting to users
data[].links.agreement_url — direct link to the agreement in the Common Paper app
Get Single Agreement
GET /v1/agreements/{id}
Create Agreement
POST /v1/agreements
IMPORTANT: The create endpoint does NOT use JSONAPI format. It uses a flat structure with these top-level keys:
{
"owner_email": "owner@company.com",
"template_id": "uuid-of-template",
"draft": false,
"agreement": {
"agreement_type": "NDA",
"sender_signer_name": "Jane Smith",
"sender_signer_title": "CEO",
"sender_signer_email": "jane@company.com",
"recipient_email": "recipient@example.com",
"recipient_name": "John Doe"
}
}
Required fields:
owner_email — email of the user who will own this agreement (must be a user in the org)
template_id — UUID of the template to use (get from GET /v1/templates)
agreement.recipient_email — recipient's email address
agreement.recipient_name — recipient's name (REQUIRED — will error without it)
Top-level options:
draft — boolean (top-level, NOT nested under agreement). When true, the agreement is created with status draft and is not sent to the recipient. Send it later via POST /v1/agreements/{id}/send. When false or omitted, the agreement is created and sent immediately.
Default to draft mode. When creating an agreement on the user's behalf, always pass draft: true unless the user explicitly says to send it right away. Then tell the user the draft was created, give them the agreement URL to review, and ask whether to send it. Only call POST /v1/agreements/{id}/send after they confirm.
Optional but recommended fields:
agreement.sender_signer_name, agreement.sender_signer_title, agreement.sender_signer_email
agreement.recipient_organization, agreement.recipient_title
agreement.agreement_type — NDA, CSA, etc.
agreement.test_agreement — set to true to mark as a test; still sends a real email and requires a real recipient, but is noted as not legally binding and does not count against monthly limits
cc_users — array of email addresses to CC on the agreement (must be users in the org)
Billing fields:
agreement.include_billing_workflow — set to true to enable a billing workflow after signing
agreement.billing_workflow_type — "link" or "stripe"
agreement.payment_link_url — payment URL shown after signing; requires include_billing_workflow: true and billing_workflow_type: "link"
agreement.include_automated_payment_reminders — boolean; sends automatic payment reminders when true
agreement.include_billing_info — boolean; enables billing contact fields
agreement.billing_name — billing contact name; used when include_billing_info is true
agreement.billing_email — billing contact email; used when include_billing_info is true
Governing law fields:
agreement.governing_law_country — country whose laws govern the agreement (e.g., "United States of America")
agreement.governing_law_region — state or region whose laws govern the agreement (e.g., "Delaware")
agreement.chosen_courts_country — country where disputes will be resolved
agreement.chosen_courts_region — state or region where disputes will be resolved
agreement.district_or_county — district or county for dispute resolution
Notice email fields:
agreement.sender_notice_email_address — email for legal notices to the sender
agreement.recipient_notice_email_address — email for legal notices to the recipient
Framework terms fields:
agreement.framework_terms_type — "new" (standard) or "description" (custom)
agreement.framework_terms_description — custom framework terms; only used when framework_terms_type is "description"
Other fields:
agreement.manual_send — set to true to mark the agreement as manually sent outside the platform
agreement.message — optional plain-language note included when the agreement is sent to the recipient
Presenting an agreement to a counterparty
Common Paper agreements use a Cover Page + Standard Terms structure: the cover page holds the deal-specific terms (parties, term, fees, governing law), and incorporates the published, version-numbered standard terms by reference.
For counterparties unfamiliar with this model, use agreement.message to include a short note with the agreement email, e.g. "This is a Common Paper standard agreement. The terms are published at https://commonpaper.com/standards. Only the cover page is specific to this deal."
Common Paper also publishes a one-page explainer, "Why We Use Common Paper Standard Agreements", which can be linked or attached separately if more context is needed.
Agreement Actions
POST /v1/agreements/{id}/send — Send a draft agreement to the recipient
PATCH /v1/agreements/{id}/void — Void an agreement
PATCH /v1/agreements/{id}/reassign — Reassign recipient
PATCH /v1/agreements/{id}/resend_email — Resend signature email
GET /v1/agreements/{id}/history — Get agreement history
GET /v1/agreements/{id}/download_pdf — Download PDF
GET /v1/agreements/{id}/shareable_link — Get shareable link
Send: Only works on agreements in draft status (returns 422 otherwise). No request body required.
Templates
GET /v1/templates — List all templates (paginated; default page size 15)
GET /v1/templates/{id} — Get single template
POST /v1/templates — Create a new template
PATCH /v1/templates/{id} — Update an existing template
Compact vs full list responses: By default, the templates list includes shared attributes only (name, version, roles, agreement_type, etc.). Type-specific attributes and nested objects (such as governing_law_region, template_csa_order_form, or template_psa_statement_of_work) are excluded. Pass full=true to include those in the list response. Use GET /v1/templates/{id} when you only need one template's full configuration.
GET /v1/templates?full=true
When to use full=true on list:
- Inspecting order form defaults, fees, or type-specific settings across multiple templates
- Comparing template configurations without a separate GET per template
When the compact list is enough:
- Finding a template ID by type (
type == "template_nda")
- Listing template names for the user to pick from
Templates have a type field in the response indicating the agreement type: template_nda, template_csa, template_dpa, template_design, template_psa, template_partnership, template_baa, template_loi, template_software_license, template_pilot.
When creating an agreement, look up the appropriate template by matching the type field. For example, to send an NDA, find the template where type == "template_nda".
Creating a Template
POST /v1/templates
The type field is required and must be one of the short names (not the template_* response values):
| Short name | Agreement type |
|---|
NDA | Mutual Non-Disclosure Agreement |
CSA | Cloud Service Agreement |
DPA | Data Processing Agreement |
Design | Design Partner Agreement |
Pilot | Pilot Agreement |
PSA | Professional Services Agreement |
Partnership | Partnership Agreement |
BAA | Business Associate Agreement |
LOI | Letter of Intent |
Software License | Software License Agreement |
All other fields in the request body are optional and set the defaults pre-filled when agreements are created from the template.
Common parameters (all types):
name — template name (required, human-readable)
governing_law_country — defaults to "United States of America"
governing_law_region — state/region (e.g., "Delaware", "California")
chosen_courts_country — country for dispute resolution
chosen_courts_region — state/region for dispute resolution
district_or_county — district or county for courts (e.g., "Court of Chancery")
negotiations_allowed — boolean (default: true)
sender_role — label for your role (e.g., "Provider", "Company")
recipient_role — label for counterparty role (e.g., "Customer", "Partner")
sender_last_to_sign — boolean; when true, sender countersigns after recipient
default_signer_email — pre-fill the signer's email on new agreements
include_additional_changes — boolean; allow freeform additional terms
effective_date_type — "signature" (on signing date) or "date" (specific date)
NDA-specific parameters:
term — term length in years (integer; default: 1)
term_period_perpetual — boolean; if true, term never expires
confidentiality_period — confidentiality period in years (integer; default: 2)
confidentiality_period_perpetual — boolean; perpetual confidentiality
purpose — purpose statement (default: "Evaluating whether to enter into a business relationship…")
include_terms — boolean; include standard CP terms on the agreement
CSA versions: Common Paper has two major CSA versions. The API defaults to the latest (currently v2.1). Pass standard_version: "1.0" or "2.0" to override. Most new accounts should use v2.
CSA-specific parameters (both versions):
general_cap_amount_type — "preceding" (multiple of fees paid), "qty" (fixed dollar amount), or "unlimited"
general_cap_preceding_amount — multiplier if type is "preceding" (e.g., "1.0" = 1× fees)
general_cap_amount — fixed cap if type is "qty" (dollar amount as string)
include_covered_claims — boolean; include indemnification coverage
include_covered_claims_include_provider_claims — boolean
include_covered_claims_provider_claims — text of provider IP indemnification
include_covered_claims_include_customer_claims — boolean
include_covered_claims_customer_claims — text of customer claims
include_increased_cap_amount — boolean
include_increased_claims — boolean
include_increased_claims_breach_of_privsec — boolean
include_increased_claims_breach_of_confidentiality — boolean
include_unlimited_claims — boolean
include_unlimited_claims_indemnification_obligation — boolean
include_unlimited_claims_breach_of_confidentiality — boolean
include_security_policy — boolean
include_security_policy_include_reasonable_efforts — boolean
include_security_policy_include_annual_maintenance — boolean; must be set to true when enabling any certification flags below, or the certifications will not render on the agreement
include_security_policy_soc2 — boolean
include_security_policy_soc2_type2 — boolean
include_security_policy_iso27001 — boolean
include_security_policy_penetration_testing — boolean
include_acceptable_use_policy — boolean
include_additional_warranties — boolean
include_insurance_minimums — boolean
include_insurance_minimums_include_general_liability — boolean
include_insurance_minimums_general_liability_minimum — string, e.g. "1000000.0"
include_insurance_minimums_general_liability_aggregate — string
include_publicity_rights — boolean
include_billing_workflow — boolean
billing_workflow_type — "stripe" or "link"
v1-only CSA parameters:
include_security_policy_hipaa — boolean (v1 only; use BAA template for HIPAA compliance in v2)
v2-only CSA parameters:
include_security_policy_hitrust — boolean
include_ai_addendum — boolean; requires include_ai_addendum_attachment_id (upload PDF first)
include_ai_addendum_attachment_id — UUID from POST /v1/attachments
framework_terms_type — required for v2 — "new" (standard CP terms) or "description" (custom)
include_dpa — boolean; include DPA addendum (v2 only; requires include_dpa_type)
include_dpa_type — "description" (inline text) or "attachment" (requires include_dpa_attachment_id)
CSA order form (template_csa_order_form_attributes):
cloud_service_description — required — description of the cloud service
subscription_period — integer (default: 1)
subscription_period_unit — "year(s)" or "month(s)"
subscription_auto_renew — "days" (rolling) or "no"
subscription_renewal_notice_days — integer (default: 30)
include_sla — boolean
include_product_support — boolean
include_professional_services — boolean
include_free_trial — boolean
free_trial_days — integer
include_max_users — boolean
max_users — integer
fees_currency — "USD" (default)
fees_inclusive_of_taxes — boolean
fees_include_fee_increase_upon_renewal — boolean
fees_include_fee_increase_upon_renewal_fee — percentage as decimal (e.g., "5.0" = 5%)
v1 order form payment fields:
payment_period — integer days until payment due (e.g., 30)
payment_period_unit — "day(s)"
invoice_period — "annually" or "monthly"
affiliates_as_users — boolean
v2 order form payment fields:
payment_process_type — required for v2 — "automatic" (Stripe recurring), "invoice", or "custom"
payment_process_automatic_frequency — required if payment_process_type is "automatic" (e.g., "monthly", "annually")
payment_process_type_invoice_type — billing frequency, required if type is "invoice" (e.g., "annually", "monthly")
payment_process_type_invoice_amount — net-X count (integer), required if type is "invoice" (e.g., 30 for net-30)
payment_process_type_invoice_duration — net-X unit, required if type is "invoice" (e.g., "day")
include_pilot_period — boolean; enable a pilot/trial period before full subscription
include_pilot_period_duration_amount — integer
include_pilot_period_duration_type — e.g., "day(s)", "month(s)"
include_pilot_period_include_fees — boolean; whether pilot period has fees
Important: when payment_process_type is "invoice", you MUST set all of payment_process_type_invoice_type, payment_process_type_invoice_amount, and payment_process_type_invoice_duration. Missing the amount/duration crashes the agreement preview when the template is used (the presenter calls pluralize(nil, nil) and raises). For net-30 annual billing, that's { invoice_type: "annually", invoice_amount: 30, invoice_duration: "day" }.
CSA fees (v2): v2 CSAs support structured fee line items as an array under fees_attributes. Each fee has a type field:
| Fee type | Description |
|---|
Fees::Flat | Fixed total amount (cost) |
Fees::Cost | Per-unit pricing (cost × quantity) |
Fees::Metered | Usage-based (cost) |
Fees::OneTime | One-time non-recurring charge |
Fees::Discount | Discount line item (discount_type: "fixed_amount" or "percentage") |
Fees::Graduated | Tiered pricing with tiers |
Fees::Included | Included at no charge |
Fees::Text | Free-form text description only |
Fees::Attachment | Fee defined via uploaded PDF attachment |
Common fee fields: description, cost (decimal as string), quantity (integer), stripe_product_id.
PSA-specific parameters:
template_psa_statement_of_work_attributes — nested object with PSA defaults
Software License-specific parameters:
template_software_license_order_form_attributes — nested object with license defaults
Partnership-specific parameters:
template_partnership_business_terms_attributes — nested object
Pilot-specific parameters:
product_description — short description of the product being piloted (set this when creating, otherwise the template renders without context)
pilot_period_description — text describing the pilot period and its terms
pilot_period_fees_description — text used when the pilot has fees (paired with fees_type)
fees_type — "free" (default) or paid variants
include_technical_support — boolean
technical_support_description — text shown when support is included
Example: Create an NDA template
curl -s -H "Authorization: Bearer $(cat ~/.claude/skills/commonpaper/cp-api-token)" \
-H "Content-Type: application/json" \
-d '{
"type": "NDA",
"name": "Standard Mutual NDA",
"governing_law_region": "Delaware",
"chosen_courts_region": "Delaware",
"term": 1,
"confidentiality_period": 2,
"purpose": "Evaluating a potential business relationship.",
"negotiations_allowed": true
}' \
"https://api.commonpaper.com/v1/templates"
Example: Create a CSA template
curl -s -H "Authorization: Bearer $(cat ~/.claude/skills/commonpaper/cp-api-token)" \
-H "Content-Type: application/json" \
-d '{
"type": "CSA",
"name": "Standard Cloud Service Agreement",
"governing_law_region": "Delaware",
"general_cap_amount_type": "preceding",
"general_cap_preceding_amount": "1.0",
"include_security_policy": true,
"include_dpa": false,
"template_csa_order_form_attributes": {
"cloud_service_description": "SaaS platform for team collaboration",
"subscription_period": 1,
"subscription_period_unit": "year(s)",
"fee_period": "year",
"payment_period": 30,
"payment_period_unit": "day(s)",
"invoice_period": "annually"
}
}' \
"https://api.commonpaper.com/v1/templates"
The response is a JSONAPI object. The new template's UUID is in data.id.
Updating a Template
PATCH /v1/templates/{id}
Pass only the fields you want to change. All the same fields from the create endpoint are valid. Nested objects like template_csa_order_form_attributes are also accepted.
Example: Update an NDA template's jurisdiction
curl -s -H "Authorization: Bearer $(cat ~/.claude/skills/commonpaper/cp-api-token)" \
-H "Content-Type: application/json" \
-d '{
"governing_law_region": "California",
"chosen_courts_region": "California",
"district_or_county": "Superior Court of Santa Clara County"
}' \
"https://api.commonpaper.com/v1/templates/{id}"
Note: There is no delete endpoint for templates. Templates can be renamed but not removed via the API.
Attachments
POST /v1/attachments — Upload a file attachment
Attachments are files (PDFs, policies, addenda) that can be referenced from templates. Upload uses multipart/form-data.
Request fields (inside the attachment form object):
pdf — the file to upload (required)
name — display name for the attachment (required)
description — optional description
uploaded_by — optional email of the uploader
Example:
curl -s -X POST \
-H "Authorization: Bearer $(cat ~/.claude/skills/commonpaper/cp-api-token)" \
-F "attachment[name]=Security Policy" \
-F "attachment[description]=Our information security policy" \
-F "attachment[uploaded_by]=legal@company.com" \
-F "attachment[pdf]=@/path/to/security-policy.pdf" \
"https://api.commonpaper.com/v1/attachments"
The response includes the attachment ID, which can then be referenced in template fields like include_security_policy_attachment_id, include_dpa_attachment_id, include_acceptable_use_policy_attachment_id, etc.
Other Endpoints
GET /v1/organizations — List organizations (returns the caller's org)
GET /v1/organizations/{id} — Get single organization
GET /v1/users — List organization users
GET /v1/users/{id} — Get single user
GET /v1/agreement_types — List all agreement types
GET /v1/agreement_statuses — List all agreement statuses
GET /v1/agreement_history — List agreement histories (filterable; paginated; default page size 15)
Organizations
The token is scoped to a single organization. GET /v1/organizations returns that org as a single-element list. Useful attributes include name (the company name), street_address, city, state, zipcode, country, and onboarded. Use this endpoint to fetch the company name rather than asking the user for it.
Users
The /v1/users endpoint returns user details including name, email, and title. Use this to look up sender information when creating agreements — the user may only provide an email, and you can fill in name/title from the user record.
Filtering (Ransack)
The API supports filtering via query parameters using ransack predicates. The syntax is filter[field_predicate]=value.
Predicates
| Predicate | Meaning | Example |
|---|
_eq | Equals | filter[status_eq]=signed |
_not_eq | Not equal | filter[status_not_eq]=draft |
_cont | Contains (case-insensitive) | filter[recipient_organization_cont]=microsoft |
_i_cont | Contains (case-insensitive, explicit) | filter[recipient_organization_i_cont]=google |
_gt | Greater than | filter[effective_date_gt]=2024-01-01 |
_lt | Less than | filter[end_date_lt]=2025-12-31 |
_gteq | Greater than or equal | filter[end_date_gteq]=2025-01-01 |
_lteq | Less than or equal | filter[end_date_lteq]=2025-12-31 |
_present | Field is not null/empty | filter[end_date_present]=true |
_blank | Field is null/empty | filter[end_date_blank]=true |
_in | In a list | filter[status_in][]=signed&filter[status_in][]=sent_to_recipient_for_signature |
_null | Is null | filter[end_date_null]=true |
_not_null | Is not null | filter[end_date_not_null]=true |
Combining Filters
Chain multiple filter params: filter[status_eq]=signed&filter[agreement_type_eq]=NDA
Filterable Fields on Agreements
All agreement columns are filterable, including nested model fields prefixed with the model name. Key fields:
Identity & Type:
agreement_type — NDA, CSA, DPA, PSA, Design, Partnership, BAA, LOI, Software License, Amendment, Pilot (or custom)
status — see Agreement Statuses below
description
Parties:
sender_signer_name, sender_signer_email, sender_signer_title
sender_organization
recipient_name, recipient_email, recipient_title
recipient_organization
Dates:
effective_date — when the agreement takes effect
end_date — expiration date
sent_date — when it was sent
all_signed_date — when fully executed
sender_signed_date, recipient_signed_date
Terms:
term — term length (integer, in units of term_period)
term_period_perpetual — boolean
purpose
governing_law_country, governing_law_region
Financial:
ai_gmv — gross contract value (available on all agreement types, useful for sorting by deal size)
ai_arr — annual recurring revenue
csa_order_form_total_contract_value — total contract value (CSA-specific)
csa_order_form_subscription_fee — subscription fee amount
csa_order_form_payment_period — payment frequency
Roles (nested):
agreement_roles_user_email — filter by user email in any role
Other nested models:
csa_*, csa_order_form_*, dpa_*, design_partner_*, psa_*, psa_statement_of_work_*, partnership_*, partnership_business_terms_*, baa_*, loi_*
Agreement Statuses
The status field contains the internal status. The display_status field contains a human-readable version. Prefer display_status when presenting results to users.
Internal statuses:
draft — Not yet sent
sent_waiting_for_initial_review — Sent, awaiting first review
sent_waiting_for_sender_review — Waiting for sender to review
sent_waiting_for_recipient_review — Waiting for recipient to review
in_progress — Active negotiation
sent_to_recipient_for_signature — Sent for recipient signature
sent_to_sender_signer_for_signature — Sent for sender signature
waiting_for_sender_signature — Awaiting sender signature
waiting_for_recipient_signature — Awaiting recipient signature
signed — Fully executed (display_status: "Completed")
signed_waiting_for_final_confirmation — Signed, pending confirmation
declined_by_recipient — Recipient declined
voided_by_sender — Voided
sent_manually — Sent outside the platform
Signed/active = status_eq=signed
In-flight = any status that is not draft, signed, voided_by_sender, or declined_by_recipient
Pagination
These list endpoints are paginated and share the same defaults:
GET /v1/agreements
GET /v1/templates
GET /v1/agreement_history
GET /v1/agreements and GET /v1/templates also accept full=true to return type-specific nested attributes in list responses (see those endpoint sections above). Default list responses are compact.
They return pagination metadata in meta.pagination:
records — total number of matching results (use this for counts!)
current — current page number
next — next page number (null if on last page)
last — last page number
Use page[number]=N&page[size]=M query params (URL-encoded: page%5Bnumber%5D=N&page%5Bsize%5D=M). Default page size is 15.
For counting: Use page%5Bsize%5D=1 and read meta.pagination.records — no need to paginate through all results.
For complete lists: Paginate through all pages when the user wants a full list. Check for meta.pagination.next and keep fetching until it's null.
Common Query Patterns
In all examples below, $AUTH refers to -H "Authorization: Bearer $(cat ~/.claude/skills/commonpaper/cp-api-token)".
"How many signed contracts do I have?"
curl -s $AUTH \
"https://api.commonpaper.com/v1/agreements?filter%5Bstatus_eq%5D=signed&page%5Bsize%5D=1" | jq '.meta.pagination.records'
"Do I have any contracts with {Company}?"
Use _cont for case-insensitive partial matching on recipient_organization. Also check sender_organization in case the user's org is the recipient.
"Do I have an active NDA with {Company}?"
Combine agreement type, status, and company filters. An "active" NDA is signed and not expired:
filter[agreement_type_eq]=NDA&filter[status_eq]=signed&filter[recipient_organization_cont]={Company}&filter[expired_eq]=false
"Who was the signer on the {Company} account?"
Find agreements with that company and extract signer info from attributes: sender_signer_name, sender_signer_email, recipient_name, recipient_email.
"Largest CSA deal by total contract amount?"
Fetch all signed CSAs and sort client-side by ai_gmv with jq:
curl -s $AUTH \
"https://api.commonpaper.com/v1/agreements?filter%5Bagreement_type_eq%5D=CSA&filter%5Bstatus_eq%5D=signed&page%5Bsize%5D=100" \
| jq '[.data[] | {counterparty: .attributes.recipient_organization, gmv: (.attributes.ai_gmv // "0" | tonumber), summary: .attributes.summary}] | sort_by(-.gmv)'
Note: ai_gmv may be "0" or null for some agreements even if fees exist — check the summary field and nested fee data (csa_order_form.fees) for the full picture. Nested fee data is not included in the default list response; pass full=true on the list call or fetch individual agreements with GET /v1/agreements/{id}.
"Upcoming renewal dates?"
curl -s $AUTH \
"https://api.commonpaper.com/v1/agreements?filter%5Bstatus_eq%5D=signed&filter%5Bend_date_gteq%5D=$(date +%Y-%m-%d)&sort=end_date&page%5Bsize%5D=100"
Present results as a table with columns: Agreement Type, Counterparty, End Date, Term.
Response Handling
Presenting Results
- Format results as readable tables or summaries
- Use
display_status (not status) when showing status to users
- For agreement lists, include: Agreement Type, Counterparty (recipient_organization), Status (display_status), Effective Date, End Date
- For signer queries, include: Name, Email, Title, Organization
- For financial queries, include currency amounts formatted properly
- When counting, give exact numbers from
meta.pagination.records
- For date-based reports, sort chronologically and group logically
- Include
links.agreement_url when the user might want to view the agreement in the app
Error Handling
- 400 Bad Request: Check the request body schema — the create endpoint uses a specific format (see Create Agreement above), not JSONAPI.
- 401 Unauthorized: Token is invalid or expired. Ask the user to check their API token.
- 403 Forbidden / "You've reached your plan limit": On a new account this almost always means the user's email is not verified rather than an actual plan limit. Check
email_verified on GET /v1/users and tell the user to verify their email before trying again. Test agreements bypass plan limits but still require a verified email.
- 404 Not Found: The agreement or resource doesn't exist.
- 422 Unprocessable Entity: Invalid filter or request body. Check the filter syntax.
Write Operations
Create Template
To create a template:
- Confirm the type — ask the user which agreement type they want (NDA, CSA, DPA, etc.)
- Collect parameters — ask for name and any key settings (jurisdiction, term, etc.) or apply sensible defaults
- For CSA only —
cloud_service_description is required; ask the user for it if not provided
- Confirm details with the user before creating
- POST to
/v1/templates
The response includes the new template ID in data.id. Show the template name and ID to the user.
Update Template
To update an existing template:
- Identify the template — list templates if the user didn't specify which one, then confirm
- Confirm the changes before patching
- PATCH to
/v1/templates/{id} with only the fields being changed
Create Agreement
To create an agreement:
- Look up the sender using
GET /v1/users to get their name, title, and email
- Look up the template using
GET /v1/templates and find the one matching the desired type (e.g., type == "template_nda"). The compact list is sufficient for ID lookup; use full=true or GET /v1/templates/{id} only if you need to read template defaults before creating.
- Confirm details with the user — be explicit about what you're going to do before you do it (a Cowork user may be watching without having initiated the request)
- POST to
/v1/agreements with draft: true by default:
curl -s -H "Authorization: Bearer $(cat ~/.claude/skills/commonpaper/cp-api-token)" \
-H "Content-Type: application/json" \
-d '{
"owner_email": "owner@company.com",
"template_id": "uuid-of-template",
"draft": true,
"agreement": {
"agreement_type": "NDA",
"sender_signer_name": "Jane Smith",
"sender_signer_title": "CEO",
"sender_signer_email": "jane@company.com",
"recipient_email": "recipient@example.com",
"recipient_name": "John Doe"
}
}' \
"https://api.commonpaper.com/v1/agreements"
- Show the user the draft URL (
data.links.agreement_url) and ask whether to send it
- If they confirm, send it via
POST /v1/agreements/{id}/send
Always default to draft: true. Only set draft: false (or omit it) if the user has explicitly said to send the agreement immediately without review. When sending immediately, restate that the email will go out right away before posting.
Send Draft Agreement
POST /v1/agreements/{id}/send
Sends a previously created draft. The agreement must be in draft status — sending an already-sent agreement returns 422. No request body needed.
curl -s -X POST -H "Authorization: Bearer $(cat ~/.claude/skills/commonpaper/cp-api-token)" \
"https://api.commonpaper.com/v1/agreements/{id}/send"
Void Agreement
Always confirm with the user before voiding.
Reassign Recipient
Requires recipient_email and recipient_name in the request body.
Resend Email
Simple PATCH to /v1/agreements/{id}/resend_email.
Workflow
- Load or request credentials (check saved token file first)
- Validate token with a test API call if this is the first use
- Understand the user's question — map it to the right endpoint and filters
- Sanitize any user-provided filter values — URL-encode and strip dangerous characters
- Make API call(s) — read token from file via
$(cat ...), use filtering to narrow results server-side when possible
- Paginate if needed — for counts, just read
meta.pagination.records; for full lists, paginate through all pages
- Present results clearly — tables, summaries, or direct answers. Never include credentials in output.
- For write operations — look up user/template info first, confirm details with user, then execute
Provisioning a New Account (Agentic Signup)
Use this flow when the user asks to create a brand-new Common Paper account from scratch via the API — phrases like "provision a new account", "create a fresh Common Paper account", "sign me up for Common Paper", or "create an agentic signup account for X". This is for net-new orgs, not for using an existing one.
The flow is two unauthenticated public endpoints on the gateway. No prior API key is needed.
Step 1: Mint a provision key
curl -s -X POST "https://api.commonpaper.com/v1/keys" \
-H "Content-Type: application/json" \
-d '{"contact_email":"YOUR_CONTACT_EMAIL","integrator_name":"claude-code"}'
contact_email is the operator/agent's email (a contact point if the key needs follow-up), not the email of the user being signed up. Throwaway domains (mailinator, tempmail, guerrillamail, 10minutemail, throwawaymail) are rejected with 422.
integrator_name is optional metadata for attribution. Use claude-code when running this from the skill.
The response is JSON with a one-time key (used as a Bearer token in Step 2), plus key_last_four, contact_email, and integrator_name. Capture the key — it's not shown again, and once redeemed it's revoked at the gateway.
Rate limits: 5 req/minute and 50 req/day per IP. If the user is testing the flow repeatedly, expect 429s after the first few.
Step 2: Redeem for an account
curl -s -X POST "https://api.commonpaper.com/v1/accounts" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_PROVISION_KEY" \
-d '{"email":"NEW_USER_EMAIL","name":"NEW_USER_NAME","org_name":"NEW_ORG_NAME"}'
All three fields are required. The new user is created as an admin of the new org. The response includes api_key (the production-bucket key the agent uses from here on), user_id, and organization_id. We also queue up two emails to the new user's address: One is a verification email and the other is a password reset email.
Possible non-201 responses:
- 404 Not Found — provision key doesn't exist
- 410 Gone — provision key has already been redeemed (single-use; this is the most common confusion if you try to re-run Step 2 with a stale key)
- 409 Conflict — email is already in use
- 422 Unprocessable Entity — required field missing
- 502 Bad Gateway — Zuplo or Auth0 failure
Step 3: Install the new API key for the rest of the skill
The new account's API key needs to be in cp-api-token for the skill to use it. If a token is already saved, back it up first.
if [ -f ~/.claude/skills/commonpaper/cp-api-token ]; then
cp ~/.claude/skills/commonpaper/cp-api-token ~/.claude/skills/commonpaper/cp-api-token.bak
fi
printf '%s' 'NEW_API_KEY' > ~/.claude/skills/commonpaper/cp-api-token
chmod 600 ~/.claude/skills/commonpaper/cp-api-token
Validate with the standard test call (GET /v1/agreements?page%5Bsize%5D=1) and confirm 200 before continuing.
Tell the user the new account is provisioned, mention that the new user will receive verification and password reset emails, and offer to restore the original token from the backup at the end of the session (or whenever they're done with the new account).
What's immediately usable vs. blocked
After Step 2, the new account is ready for most read and setup work without further action:
- Templates: create, update, list
- Org info: read, update
- Users: read
What's still gated until the new user clicks the email verification link:
POST /v1/agreements/{id}/send — and any agreement create that omits draft: true (since that immediately sends). Surfaces as 403 with a "plan limit" message, which is misleading — it's actually the verification gate.
test_agreement: true bypasses plan limits but still requires verification before send.
Draft creation itself (POST /v1/agreements with draft: true) is not gated by verification — drafts can be created on an unverified account. Templates, org reads/writes, and user reads also work pre-verification, so Onboarding Mode (below) runs fine on a fresh account. Only the send step is blocked.
Chaining with Onboarding Mode
A common pattern is "provision a new account and then onboard it." Run Steps 1–3 above, then transition straight into Onboarding Mode below using the new account's token. The fresh org will have zero templates, so the full Q&A is appropriate.
Onboarding Mode
Onboarding mode guides a brand-new user through setting up their entire Common Paper account from scratch. Trigger it when the user asks to "set up my account", "onboard", or "create all my templates".
IMPORTANT — Legal disclaimer: Always display this at the start of onboarding:
⚠️ Not legal advice. The suggestions here are common starting points based on your answers, not legal recommendations. Have a qualified attorney review your final templates before use.
Phase 1: Account setup
Confirm the API token is saved (standard credential flow). Then fetch the company name from GET /v1/organizations (data[0].attributes.name) — do not ask the user for it. Use this name to prefix template names in Phase 4. Then explain what onboarding will do: ask a few quick questions about their business, then create a sensible set of contract templates tailored to their answers.
Phase 2: Q&A — show all questions, answer one at a time
Display all questions upfront so the user can see the full scope, then ask them to answer one at a time starting with the first. This lets them see how much work is involved without requiring a single long response.
Keep it conversational and brief — the user should be able to answer in under 5 minutes. Do not ask about legal specifics; derive those from their answers.
Here's what I'll need to know to set up your templates. I'll ask you one at a time — let's start with the first:
1. What does your product or service do? (1–2 sentences)
2. What state or country's law do you want governing your agreements? (This is a legal preference, not necessarily where you operate. Don't suggest a specific jurisdiction — let the user decide.)
3. What's the email of the person who will typically send and sign agreements? (Must be an existing user in your Common Paper account — probably the email you signed up with)
4. Do you sell software as a service (SaaS), on-premise/embedded software, or both?
5. Do you offer professional services? (implementation, consulting, custom dev)
6. Do you have a free trial or pilot program? (If yes, also ask: do you want this as a **standalone Pilot template**, **embedded in the CSA** as a pilot period before the subscription kicks in, or **both**?)
7. Do any of your customers work in healthcare, or does your product process health records?
8. Do you have customers in Europe, or does your product process personal data from EU residents?
9. Does your product use AI or machine learning in a way that's visible to your customers?
10. Do you have security certifications? (e.g., SOC 2, ISO 27001, HITRUST — or none yet)
I have your company name from your Common Paper account already — let's start with the first question: what does your product or service do?
After each answer, acknowledge it briefly and ask the next question. Once all answers are collected, move to Phase 3.
Phase 3: Interpret answers and propose a plan
Based on the user's answers, decide which templates to create and what key settings to apply. Use this mapping as your guide — apply judgment, don't apply it mechanically:
Always create: NDA (nearly every company needs one)
Create CSA if: they sell SaaS or any subscription software
Create Software License if: they sell on-premise or embedded software
Defer DPA to the wrap-up checklist if they have EU customers or process personal data on behalf of customers. Do not create a DPA template during onboarding — the API alone can't capture the configuration these need (subprocessors, transfer mechanisms, attachment, etc.). Note in the plan that DPA setup needs a dedicated flow and add it to the post-onboarding checklist.
Defer BAA to the wrap-up checklist if they mention healthcare customers or processing health records. Same reason — BAA configuration is too thin via the API alone. Note in the plan and surface it in the post-onboarding checklist.
Scope of the deferral: this only applies to the auto-onboarding flow, where defaults are inferred from a generic Q&A. If the user explicitly asks to create a DPA or BAA template (e.g., "make me a BAA template") outside onboarding, fall back to the standard Create Template flow and create it — collect the parameters from them directly.
Create PSA if: they offer professional services, implementation, or consulting
Create Design Partner if: they have a pilot or early-access program with design partners
Create Pilot template if: they want a standalone pilot agreement (or "both"). When creating, set product_description to a one-line description of what's being piloted (derived from their answer to Q1). If they only want it embedded in the CSA, skip this template.
Embed pilot in CSA if: they want it bundled with the subscription. Set include_pilot_period: true, include_pilot_period_duration_amount, include_pilot_period_duration_type (e.g., "day(s)"), and include_pilot_period_include_fees: false (unless they said the pilot has fees) on the CSA template.
Create Partnership if: they work with resellers, referral partners, or co-sell partners
Create LOI if: they mention large enterprise deals, M&A, or complex pre-contract negotiations (otherwise skip)
Key settings to extrapolate:
- Governing law / courts: Use the state they named.
- Liability cap (CSA): Default to 1× fees paid in the preceding 12 months — standard for most SaaS.
- DPA on CSA: Do NOT set
include_dpa: true on the CSA template — the reference requires a URL or attachment to be meaningful, and leaving it blank creates a broken addendum. Instead, create a standalone DPA template. Note in the plan that customers needing a DPA will sign that as a separate agreement.
- AI addendum on CSA: Requires a PDF attachment uploaded first via
POST /v1/attachments. If the user has an AI addendum PDF during onboarding, upload it and set include_ai_addendum: true with include_ai_addendum_attachment_id pointing to the returned attachment ID. If not, skip it and include it in the wrap-up to-do list.
- Security policy on CSA: Enable
include_security_policy: true if they have any certifications. Also set include_security_policy_include_annual_maintenance: true — without this flag, the certifications below won't render on the generated agreement. Then set the relevant cert flags based on what they listed: include_security_policy_soc2, include_security_policy_soc2_type2, include_security_policy_iso27001, include_security_policy_hitrust, include_security_policy_penetration_testing, etc. Note: HITRUST is a certification framework; HIPAA is a regulatory requirement — treat them separately.
- Payment terms: Default to net-30 (30 days), annual invoicing. Adjust to monthly if they mentioned monthly billing.
- Free trial: Enable on CSA order form if they said yes.
- Default signer email: Use the email from question 3 (signer) on all templates.
- Negotiations: Default to
true (allowed) for all types except DPA and BAA, which default to false.
Before creating anything, present the plan clearly and wait for explicit confirmation. Do not proceed until the user says yes — a Cowork user may be watching without having initiated the request themselves.
Based on your answers, here's what I'm going to create in your Common Paper account:
Templates:
- ✓ NDA — mutual, 1-year term, 2-year confidentiality, [State] law
- ✓ CSA — 1× preceding fees liability cap, net-30, annual billing[, + AI addendum][, SOC 2/HITRUST security policy]
- ✓ DPA — [if applicable]
- ✓ PSA — [if applicable]
- (skipping LOI — not needed based on your answers)
...
Key settings applied to all:
- Governing law: [State]
- Default signer: [email]
- Negotiations allowed: [yes/no per type]
Ready to create these — does this look right, or would you like to change anything first?
Do not create any templates until the user explicitly confirms.
Phase 4: Create the templates
Before creating, verify the signer email against GET /v1/users to confirm it exists in the account. If it doesn't match any user, ask the user to correct it — default_signer_email must be a valid account member.
Required nested fields by type — these must be included or the API will return an error:
- CSA:
template_csa_order_form_attributes.cloud_service_description
- Software License:
template_software_license_order_form_attributes.software_description
- PSA:
template_psa_statement_of_work_attributes.services_description
- Pilot: set
product_description so the template has context
CSA invoice payment trap: when v2 CSA uses payment_process_type: "invoice", you must set all three of payment_process_type_invoice_type, payment_process_type_invoice_amount, and payment_process_type_invoice_duration inside template_csa_order_form_attributes. Missing the amount/duration will not error on template create but will crash with pluralize(nil, nil) the moment someone previews an agreement built from the template. For net-30 annual: { invoice_type: "annually", invoice_amount: 30, invoice_duration: "day" }.
AI addendum: requires uploading a PDF via POST /v1/attachments first, then setting include_ai_addendum: true and include_ai_addendum_attachment_id on the CSA template. Do not attempt to enable it without a valid attachment ID.
Create templates one at a time and show progress as each is created. Report each template's name and ID as it's created.
Org ID: Do not rely on the users endpoint for the org ID — it may return null for new accounts. Instead, read attributes.organization_id from any template response.
Phase 5: Wrap-up
After all templates are created, show a summary table with direct links to each template in the app. Template app URLs follow this pattern based on type:
| Response type | App URL path |
|---|
template_nda | /organizations/{org_id}/nda/{id} |
template_csa | /organizations/{org_id}/csa/{id} |
template_dpa | /organizations/{org_id}/dpa/{id} |
template_psa | /organizations/{org_id}/psa/{id} |
template_baa | /organizations/{org_id}/baa/{id} |
template_loi | /organizations/{org_id}/loi/{id} |
template_pilot | /organizations/{org_id}/pilot/{id} |
template_design | /organizations/{org_id}/design/{id} |
template_partnership | /organizations/{org_id}/partnership/{id} |
template_software_license | /organizations/{org_id}/software_license/{id} |
Base URL: https://app.commonpaper.com
Present the summary like this:
Template naming: Prefix with the company name fetched from GET /v1/organizations in Phase 1, e.g., "Acme Corp NDA" or "Acme Corp Cloud Service Agreement".
Then display what cannot be done via the API and must be completed manually:
Here's what to do next in the app (app.commonpaper.com):
- Invite team members — Add colleagues to your organization (Settings → Members).
- Upload your logo — Add company branding for agreements (Settings → Branding). Requires a Startup plan or higher.
- Connect Slack — Get agreement notifications and updates directly in Slack (Settings → Integrations).
- Set up Stripe — If using Stripe billing workflows, connect your account (Settings → Integrations).
- Configure webhooks — Set up real-time event webhooks (Settings → Integrations).
- Add your AI addendum — If you have an AI addendum PDF, you can upload it via the skill and link it to your CSA template.
- Preview each template — Confirm the generated language looks correct before sending live agreements.
- Set up your DPA (only if EU customers / personal data processing was indicated) — DPA configuration (subprocessors, transfer mechanisms, attachments) needs a dedicated Q&A this onboarding flow doesn't cover yet. Set the template up in the app for now, or come back to the skill once a DPA-specific flow exists.
- Set up your BAA (only if healthcare customers / PHI was indicated) — Same situation as the DPA: the right defaults require a dedicated Q&A this onboarding doesn't cover yet.
Only include items 8 and 9 in the actual checklist when the user's onboarding answers triggered them.
Want to test before going live? Set test_agreement: true on your first send. It sends a real email to the recipient and looks identical to a live agreement, but is marked as not legally binding and doesn't count against your monthly limit. Once you're confident everything looks right, send a live version (without test_agreement).
Finally, always end onboarding with:
Want a lawyer to review your templates? Common Paper works with a legal partner who can review and refine your contracts.
Look up the org ID from attributes.organization_id on any template response (not users — that field may be null for new accounts), then provide the direct link:
https://app.commonpaper.com/organizations/{org_id}/legal_support
Notes
- NEVER expose the API token in chat output or as a literal in commands
- Always URL-encode square brackets in query parameters — use
%5B for [ and %5D for ]. Unencoded brackets cause zsh errors.
- The
_cont predicate is the best choice for company name searches since it handles partial and case-insensitive matching
- When a user asks about a company, search both
recipient_organization and sender_organization fields since either party could be the counterparty
- For "active" or "current" agreements, filter on
status_eq=signed combined with expired_eq=false or end_date_gteq={today}
- The API returns JSONAPI format for reads — data is in
response.data[].attributes
- List endpoints for agreements and templates return compact payloads by default; pass
full=true when you need nested type-specific attributes in list responses
- The API uses a different format for creates — NOT JSONAPI. Use
owner_email, template_id, and agreement as top-level keys.
- Use
jq for parsing JSON responses in curl commands
- When showing users the curl command you used, replace the auth header with
$CP_TOKEN placeholder and ensure brackets are URL-encoded
- Use
display_status instead of status when presenting results to users
- The
summary field on agreements contains a human-readable summary of key terms — useful for quick overviews