shiply.now

Contracts

Customer contracts — draft, send, sign, amend, and download a signed PDF. End-to-end paperwork for the projects you run through shiply.

shiply contracts are the paper trail for the work you do for customers. A contract is drafted from a project's brief, sent to the customer on the intake portal, signed with a typed name + ESIGN consent, and — optionally — amended later when scope shifts. Once signed, every party can download a tamper-evident PDF with a built-in audit trail.

Contracts attach to projects (shiply's customer-intake workflow). One parent contract per project; amendments stack as additional pages on the same PDF.

Lifecycle

A project moves through four contract-aware states:

brief_ready ──draft──▶ contract_draft ──send──▶ contract_sent

                                            (customer signs)

                                                  building

                                          (amend later if scope shifts)

                                              still building
  • brief_ready — the customer finished the intake wizard; you can draft a contract.
  • contract_draft — the parent contract exists, fields are editable. Customer cannot see it yet.
  • contract_sent — sent to the customer. Field edits are blocked; retract to make further changes.
  • building — customer signed; work is underway. Amendments are legal here (and only here).
  • void — the contract was retracted and not recovered. The project returns to brief_ready so you can draft a fresh one.

The 8 editable contract fields

Drafts populate from the project's brief, but you can edit any of these on the dashboard's Contract panel before sending:

FieldTypeNotes
scopeSummarytextOne-paragraph scope of work. Required.
feeCentsintegerTotal fee in cents (e.g. 250000 for $2,500.00).
currencystringISO 4217 code (USD, AUD, EUR, GBP, etc.).
startDatedateISO YYYY-MM-DD. When work begins.
targetCompletionDatedateISO YYYY-MM-DD. The handoff date.
revisionCountintegerIncluded revision rounds. 0 is fine.
revisionOverageCentsintegerPer-revision fee for overages (in cents).
jurisdictionstringGoverning law (e.g. Victoria, Australia).

The contract template renders these into a 9-section Markdown body:

  1. Parties — developer and customer details.
  2. Scope of workscopeSummary verbatim.
  3. Fee and payment schedule — fee in currency, deposit/balance terms.
  4. TimelinestartDatetargetCompletionDate.
  5. Revisions — count included + overage fee.
  6. Intellectual property and ownership — assignment on full payment.
  7. Scope changes and amendments — points to the amendment workflow.
  8. Handoff and delivery — what the customer receives.
  9. Jurisdiction and signatures — governing law + typed-signature block.

The rendered Markdown is hashed (SHA-256 → contentHash on the contract row) at send time. The signed PDF embeds the hash so any later edit to the Markdown is detectable.

Sign UX (ESIGN-style)

The customer reaches the contract via the intake portal (or a deep link from the send email). They:

  1. Read the rendered Markdown inline.
  2. Type their full legal name in the signature field.
  3. Tick the ESIGN consent box ("I agree to be legally bound…").
  4. Sign.

The server records:

  • signedAt (UTC timestamp),
  • signedName (the typed name),
  • signedIpAddress (request IP),
  • signedUserAgent,
  • contentHash (the hash of the Markdown they actually agreed to).

These five fields are the audit trail. They print on the certificate page of the PDF and they're what makes the signature defensible in an ESIGN or eIDAS jurisdiction.

Amendments

Once a parent contract is signed, the project moves to building and you can issue amendments — short add-ons that change scope, fee, or the target date without redrafting the whole agreement.

Each amendment captures three deltas (all optional except scopeDelta):

  • scopeDelta — text describing the change (required).
  • feeDeltaCents — integer; positive for adds, negative for discounts.
  • targetCompletionDate — new ISO date if the timeline shifts.

Amendments follow the same draft → send → sign loop as the parent, but they don't render their own contract body — they sign a structured JSON blob that's content-hashed the same way. The signed PDF stacks one [amendment page + certificate page] pair per signed amendment after the parent.

You can have multiple signed amendments. There can only ever be one live parent contract per project — retract the existing one before drafting a new one.

PDF + audit trail

shiply contract pdf <contract-id> -o contract.pdf

The PDF is rendered server-side via the contract-pdf pipeline (B6). It contains:

  • The full Markdown body of the parent contract, paginated.
  • A certificate page with the five audit fields + the contentHash.
  • One [page + certificate] pair per signed amendment.
  • Tamper-evident: the certificate page references the same hash the customer agreed to. Re-rendering after any field change produces a different hash, which is obvious on the page.

The endpoint refuses to render before the parent is signed (409 contract_not_signed). Cache headers are private, no-store — never cache a contract PDF on a shared cache.

CLI

# Lifecycle on a project
shiply contract list <project-id>                # parent + amendments + status
shiply contract draft <project-id>               # create a draft you can edit
shiply contract show <contract-id>               # full row as JSON

# Sending + signing
shiply contract send <contract-id>               # email the customer
shiply contract pdf <contract-id> -o file.pdf    # download signed PDF

# After signing
shiply contract amend <contract-id> \
  --scope "Add a second landing page" \
  --fee-delta 50000                              # +$500.00
shiply contract amend <contract-id> \
  --scope "Push handoff back two weeks" \
  --target-date 2026-08-15

# Undo
shiply contract retract <contract-id>            # void (project → brief_ready)
shiply contract retract <contract-id> --keep-as-draft  # edit + re-send

shiply contract amend drafts the amendment AND immediately sends it — amendments are a quick-fix path. Edit the dashboard panel if you want to hold an amendment as a draft.

REST API (Authorization: Bearer shp_…)

Method & pathPurpose
GET /api/v1/projects/{id}/contractsparent + amendments for a project
POST /api/v1/projects/{id}/contractcreate a draft (project must be brief_ready)
GET /api/v1/contracts/{id}one contract — readable by dev OR by the portal cookie
PATCH /api/v1/contracts/{id}edit the 8 draft fields (draft only, dev only)
POST /api/v1/contracts/{id}/sendsend a draft (parent or amendment)
POST /api/v1/contracts/{id}/signcustomer-signed via the portal — typed name + ESIGN
POST /api/v1/contracts/{id}/amendcreate an amendment draft against a signed parent
POST /api/v1/contracts/{id}/retractretract; pass {recoverAsDraft: true} to keep as draft
POST /api/v1/contracts/{id}/reminddev-triggered nudge — emails the customer again
POST /api/v1/contracts/{id}/resendre-send the same email
GET /api/v1/contracts/{id}/viewrendered Markdown (used by the portal)
GET /api/v1/contracts/{id}/pdfsigned-contract PDF (409 until signed)

The dev surface authenticates with an API key; the customer surface authenticates with the shiply-portal-token cookie issued by /intake/{token}. The PATCH and POST routes are dev-only — the customer can read and sign but not edit.

MCP — 5 contract tools

Every shiply MCP server exposes these tools (shipped in B13):

  • contract_status{projectId} → parent + amendments + state. Same payload shape as shiply contract list.
  • contract_draft{projectId} → drafts a parent contract.
  • contract_send{contractId} → sends a draft.
  • contract_amend{parentContractId, scopeDelta, feeDeltaCents?, targetCompletionDate?} → drafts + sends an amendment.
  • contract_pdf{contractId} → returns the signed PDF as a base64 string (with mimeType: application/pdf). 409 if not yet signed.

This is the same surface as the CLI and the dashboard — an agent operating a customer project end-to-end can run the contract flow with just these five tools.

Errors

Statuserror.codeMeaning
401unauthorizedmissing or invalid API key / portal cookie
403forbiddennot your project / contract
404not_foundbad <id>
409conflictwrong state — see message (e.g. contract_not_signed)
409state_violationproject state doesn't allow this op (per the state machine)
400invalid_requestbad body shape, missing required field, bad date
422template_incompletesent with one of the 8 fields still empty

The state machine is strict — POST /send on a project that's already in contract_sent returns state_violation rather than silently re-sending. Use POST /resend to fire another email for the same contract.