commonpaper
CommonPaper/claude-skillQuery 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.
SKILL.md
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%5Band%5Din 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
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 fieldsdata[].attributes.display_status— human-friendly status (e.g., "Completed", "Waiting for counterparty") — prefer this overstatuswhen presenting to usersdata[].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": "[email protected]",
"template_id": "uuid-of-template",
"draft": false,
"agreement": {
"agreement_type": "NDA",
"sender_signer_name": "Jane Smith",
"sender_signer_title": "CEO",
"sender_signer_email": "[email protected]",
"recipient_email": "[email protected]",
"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 fromGET /v1/templates)agreement.recipient_email— recipient's email addressagreement.recipient_name— recipient's name (REQUIRED — will error without it)
Top-level options:
draft— boolean (top-level, NOT nested underagreement). Whentrue, the agreement is created with statusdraftand is not sent to the recipient. Send it later viaPOST /v1/agreements/{id}/send. Whenfalseor 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_emailagreement.recipient_organization,agreement.recipient_titleagreement.agreement_type— NDA, CSA, etc.agreement.test_agreement— set totrueto 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 limitscc_users— array of email addresses to CC on the agreement (must be users in the org)
Billing fields:
agreement.include_billing_workflow— set totrueto enable a billing workflow after signingagreement.billing_workflow_type—"link"or"stripe"agreement.payment_link_url— payment URL shown after signing; requiresinclude_billing_workflow: trueandbilling_workflow_type: "link"agreement.include_automated_payment_reminders— boolean; sends automatic payment reminders when trueagreement.include_billing_info— boolean; enables billing contact fieldsagreement.billing_name— billing contact name; used wheninclude_billing_infois trueagreement.billing_email— billing contact email; used wheninclude_billing_infois 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 resolvedagreement.chosen_courts_region— state or region where disputes will be resolvedagreement.district_or_county— district or county for dispute resolution
Notice email fields:
agreement.sender_notice_email_address— email for legal notices to the senderagreement.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 whenframework_terms_typeis"description"
Other fields:
agreement.manual_send— set totrueto mark the agreement as manually sent outside the platform
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
GET /v1/templates/{id} — Get single template
POST /v1/templates — Create a new template
PATCH /v1/templates/{id} — Update an existing template
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 resolutionchosen_courts_region— state/region for dispute resolutiondistrict_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 recipientdefault_signer_email— pre-fill the signer's email on new agreementsinclude_additional_changes— boolean; allow freeform additional termseffective_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 expiresconfidentiality_period— confidentiality period in years (integer; default: 2)confidentiality_period_perpetual— boolean; perpetual confidentialitypurpose— 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 coverageinclude_covered_claims_include_provider_claims— booleaninclude_covered_claims_provider_claims— text of provider IP indemnificationinclude_covered_claims_include_customer_claims— booleaninclude_covered_claims_customer_claims— text of customer claimsinclude_increased_cap_amount— booleaninclude_increased_claims— booleaninclude_increased_claims_breach_of_privsec— booleaninclude_increased_claims_breach_of_confidentiality— booleaninclude_unlimited_claims— booleaninclude_unlimited_claims_indemnification_obligation— booleaninclude_unlimited_claims_breach_of_confidentiality— booleaninclude_security_policy— booleaninclude_security_policy_include_reasonable_efforts— booleaninclude_security_policy_include_annual_maintenance— boolean; must be set totruewhen enabling any certification flags below, or the certifications will not render on the agreementinclude_security_policy_soc2— booleaninclude_security_policy_soc2_type2— booleaninclude_security_policy_iso27001— booleaninclude_security_policy_penetration_testing— booleaninclude_acceptable_use_policy— booleaninclude_additional_warranties— booleaninclude_insurance_minimums— booleaninclude_insurance_minimums_include_general_liability— booleaninclude_insurance_minimums_general_liability_minimum— string, e.g."1000000.0"include_insurance_minimums_general_liability_aggregate— stringinclude_publicity_rights— booleaninclude_billing_workflow— booleanbilling_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— booleaninclude_ai_addendum— boolean; requiresinclude_ai_addendum_attachment_id(upload PDF first)include_ai_addendum_attachment_id— UUID fromPOST /v1/attachmentsframework_terms_type— required for v2 —"new"(standard CP terms) or"description"(custom)include_dpa— boolean; include DPA addendum (v2 only; requiresinclude_dpa_type)include_dpa_type—"description"(inline text) or"attachment"(requiresinclude_dpa_attachment_id)
CSA order form (template_csa_order_form_attributes):
cloud_service_description— required — description of the cloud servicesubscription_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— booleaninclude_product_support— booleaninclude_professional_services— booleaninclude_free_trial— booleanfree_trial_days— integerinclude_max_users— booleanmax_users— integerfees_currency—"USD"(default)fees_inclusive_of_taxes— booleanfees_include_fee_increase_upon_renewal— booleanfees_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 ifpayment_process_typeis"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.,30for 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 subscriptioninclude_pilot_period_duration_amount— integerinclude_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 termspilot_period_fees_description— text used when the pilot has fees (paired withfees_type)fees_type—"free"(default) or paid variantsinclude_technical_support— booleantechnical_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 descriptionuploaded_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][email protected]" \
-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)
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 belowdescription
Parties:
sender_signer_name,sender_signer_email,sender_signer_titlesender_organizationrecipient_name,recipient_email,recipient_titlerecipient_organization
Dates:
effective_date— when the agreement takes effectend_date— expiration datesent_date— when it was sentall_signed_date— when fully executedsender_signed_date,recipient_signed_date
Terms:
term— term length (integer, in units of term_period)term_period_perpetual— booleanpurposegoverning_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 revenuecsa_order_form_total_contract_value— total contract value (CSA-specific)csa_order_form_subscription_fee— subscription fee amountcsa_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 sentsent_waiting_for_initial_review— Sent, awaiting first reviewsent_waiting_for_sender_review— Waiting for sender to reviewsent_waiting_for_recipient_review— Waiting for recipient to reviewin_progress— Active negotiationsent_to_recipient_for_signature— Sent for recipient signaturesent_to_sender_signer_for_signature— Sent for sender signaturewaiting_for_sender_signature— Awaiting sender signaturewaiting_for_recipient_signature— Awaiting recipient signaturesigned— Fully executed (display_status: "Completed")signed_waiting_for_final_confirmation— Signed, pending confirmationdeclined_by_recipient— Recipient declinedvoided_by_sender— Voidedsent_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
Index endpoints return pagination metadata in meta.pagination:
records— total number of matching results (use this for counts!)current— current page numbernext— 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 25.
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.
"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(notstatus) 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_urlwhen 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_verifiedonGET /v1/usersand 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_descriptionis 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/usersto get their name, title, and email - Look up the template using
GET /v1/templatesand find the one matching the desired type (e.g.,type == "template_nda") - 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/agreementswithdraft: trueby default:
curl -s -H "Authorization: Bearer $(cat ~/.claude/skills/commonpaper/cp-api-token)" \
-H "Content-Type: application/json" \
-d '{
"owner_email": "[email protected]",
"template_id": "uuid-of-template",
"draft": true,
"agreement": {
"agreement_type": "NDA",
"sender_signer_name": "Jane Smith",
"sender_signer_title": "CEO",
"sender_signer_email": "[email protected]",
"recipient_email": "[email protected]",
"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. Auth0 also queues a verification email and a password reset email to the new user's address.
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 Auth0 verification link:
POST /v1/agreements/{id}/send— and any agreement create that omitsdraft: true(since that immediately sends). Surfaces as 403 with a "plan limit" message, which is misleading — it's actually the verification gate.test_agreement: truebypasses 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: trueon 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 setinclude_ai_addendum: truewithinclude_ai_addendum_attachment_idpointing 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: trueif they have any certifications. Also setinclude_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 tofalse.
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_descriptionso 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 | Link | Status |
|---|---|---|
| NDA | https://app.commonpaper.com/organizations/{org_id}/nda/{id} | ✓ Created |
| CSA | https://app.commonpaper.com/organizations/{org_id}/csa/{id} | ✓ Created |
| ... | ... | ... |
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
%5Bfor[and%5Dfor]. Unencoded brackets cause zsh errors. - The
_contpredicate 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_organizationandsender_organizationfields since either party could be the counterparty - For "active" or "current" agreements, filter on
status_eq=signedcombined withexpired_eq=falseorend_date_gteq={today} - The API returns JSONAPI format for reads — data is in
response.data[].attributes - The API uses a different format for creates — NOT JSONAPI. Use
owner_email,template_id, andagreementas top-level keys. - Use
jqfor parsing JSON responses in curl commands - When showing users the curl command you used, replace the auth header with
$CP_TOKENplaceholder and ensure brackets are URL-encoded - Use
display_statusinstead ofstatuswhen presenting results to users - The
summaryfield on agreements contains a human-readable summary of key terms — useful for quick overviews