commonpaper
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 agreements. Also use when user mentions "Common Paper", "commonpaper", or asks contract-related questions like "how many signed contracts do I have?" or "do I have an NDA with X?".
git clone https://github.com/CommonPaper/claude-skill
git clone --depth=1 https://github.com/CommonPaper/claude-skill ~/.claude/skills/commonpaper-claude-skill-commonpaper
SKILL.md- makes HTTP requests (curl)
- references API keys
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
. Never substitute in the real value. Always URL-encode brackets as-H "Authorization: Bearer $CP_TOKEN"
and%5B
in the shown command so it can be copy-pasted into zsh without errors.%5D - 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
and contain only alphanumeric characters and underscores.zpka_*
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:
— total number of matching agreements (use this for counts instead of paginating through all results)meta.pagination.records
— agreement fieldsdata[].attributes
— human-friendly status (e.g., "Completed", "Waiting for counterparty") — prefer this overdata[].attributes.display_status
when presenting to usersstatus
— direct link to the agreement in the Common Paper appdata[].links.agreement_url
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", "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:
— email of the user who will own this agreement (must be a user in the org)owner_email
— UUID of the template to use (get fromtemplate_id
)GET /v1/templates
— recipient's email addressagreement.recipient_email
— recipient's name (REQUIRED — will error without it)agreement.recipient_name
Optional but recommended fields:
,agreement.sender_signer_name
,agreement.sender_signer_titleagreement.sender_signer_email
,agreement.recipient_organizationagreement.recipient_title
— NDA, CSA, etc.agreement.agreement_type
— set toagreement.test_agreement
to send a test agreementtrue
Billing fields:
— set toagreement.include_billing_workflow
to enable a billing workflow after signingtrue
—agreement.billing_workflow_type
or"link""stripe"
— payment URL shown after signing; requiresagreement.payment_link_url
andinclude_billing_workflow: truebilling_workflow_type: "link"
— boolean; sends automatic payment reminders when trueagreement.include_automated_payment_reminders
— boolean; enables billing contact fieldsagreement.include_billing_info
— billing contact name; used whenagreement.billing_name
is trueinclude_billing_info
— billing contact email; used whenagreement.billing_email
is trueinclude_billing_info
Governing law fields:
— country whose laws govern the agreement (e.g.,agreement.governing_law_country
)"United States of America"
— state or region whose laws govern the agreement (e.g.,agreement.governing_law_region
)"Delaware"
— country where disputes will be resolvedagreement.chosen_courts_country
— state or region where disputes will be resolvedagreement.chosen_courts_region
— district or county for dispute resolutionagreement.district_or_county
Notice email fields:
— email for legal notices to the senderagreement.sender_notice_email_address
— email for legal notices to the recipientagreement.recipient_notice_email_address
Framework terms fields:
—agreement.framework_terms_type
(standard) or"new"
(custom)"description"
— custom framework terms; only used whenagreement.framework_terms_description
isframework_terms_type"description"
Other fields:
— set toagreement.manual_send
to mark the agreement as manually sent outside the platformtrue
Agreement Actions
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
Templates
GET /v1/templates — List all templates GET /v1/templates/{id} — Get single template
Templates have a
type field 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".
Other Endpoints
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) POST /v1/attachments — Upload attachment
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 |
|---|---|---|
| Equals | |
| Not equal | |
| Contains (case-insensitive) | |
| Contains (case-insensitive, explicit) | |
| Greater than | |
| Less than | |
| Greater than or equal | |
| Less than or equal | |
| Field is not null/empty | |
| Field is null/empty | |
| In a list | |
| Is null | |
| Is not null | |
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:
— NDA, CSA, DPA, PSA, Design, Partnership, BAA, LOI, Software License, Amendment, Pilot (or custom)agreement_type
— see Agreement Statuses belowstatusdescription
Parties:
,sender_signer_name
,sender_signer_emailsender_signer_titlesender_organization
,recipient_name
,recipient_emailrecipient_titlerecipient_organization
Dates:
— when the agreement takes effecteffective_date
— expiration dateend_date
— when it was sentsent_date
— when fully executedall_signed_date
,sender_signed_daterecipient_signed_date
Terms:
— term length (integer, in units of term_period)term
— booleanterm_period_perpetualpurpose
,governing_law_countrygoverning_law_region
Financial:
— gross contract value (available on all agreement types, useful for sorting by deal size)ai_gmv
— annual recurring revenueai_arr
— total contract value (CSA-specific)csa_order_form_total_contract_value
— subscription fee amountcsa_order_form_subscription_fee
— payment frequencycsa_order_form_payment_period
Roles (nested):
— filter by user email in any roleagreement_roles_user_email
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:
— Not yet sentdraft
— Sent, awaiting first reviewsent_waiting_for_initial_review
— Waiting for sender to reviewsent_waiting_for_sender_review
— Waiting for recipient to reviewsent_waiting_for_recipient_review
— Active negotiationin_progress
— Sent for recipient signaturesent_to_recipient_for_signature
— Sent for sender signaturesent_to_sender_signer_for_signature
— Awaiting sender signaturewaiting_for_sender_signature
— Awaiting recipient signaturewaiting_for_recipient_signature
— Fully executed (display_status: "Completed")signed
— Signed, pending confirmationsigned_waiting_for_final_confirmation
— Recipient declineddeclined_by_recipient
— Voidedvoided_by_sender
— Sent outside the platformsent_manually
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:
— total number of matching results (use this for counts!)records
— current page numbercurrent
— next page number (null if on last page)next
— last page numberlast
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
(notdisplay_status
) when showing status to usersstatus - 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
when the user might want to view the agreement in the applinks.agreement_url
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: Token may not have the required permissions.
- 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 Agreement
To create and send an agreement:
- Look up the sender using
to get their name, title, and emailGET /v1/users - Look up the template using
and find the one matching the desired type (e.g.,GET /v1/templates
)type == "template_nda" - Confirm details with the user before sending
- POST to
:/v1/agreements
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", "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"
The agreement will be created and sent immediately. The response includes the agreement URL in
data.links.agreement_url.
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
, use filtering to narrow results server-side when possible$(cat ...) - Paginate if needed — for counts, just read
; for full lists, paginate through all pagesmeta.pagination.records - 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
Notes
- NEVER expose the API token in chat output or as a literal in commands
- Always URL-encode square brackets in query parameters — use
for%5B
and[
for%5D
. Unencoded brackets cause zsh errors.] - The
predicate is the best choice for company name searches since it handles partial and case-insensitive matching_cont - When a user asks about a company, search both
andrecipient_organization
fields since either party could be the counterpartysender_organization - For "active" or "current" agreements, filter on
combined withstatus_eq=signed
orexpired_eq=falseend_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
, andtemplate_id
as top-level keys.agreement - Use
for parsing JSON responses in curl commandsjq - When showing users the curl command you used, replace the auth header with
placeholder and ensure brackets are URL-encoded$CP_TOKEN - Use
instead ofdisplay_status
when presenting results to usersstatus - The
field on agreements contains a human-readable summary of key terms — useful for quick overviewssummary