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 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?".
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 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?".
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",
"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)
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 send a test agreement
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
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 |
|---|---|---|
_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: 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
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 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": "[email protected]",
"template_id": "uuid-of-template",
"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"
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
$(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
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