cypress.DOCS
Log inRegister
Cypress Docs
Developers

Closings

The closings of the cars you sold and bought, step by step, with who carries each deadline and whose turn it is.

TL;DRAI summary

This chapter explains how closings work after an offer is accepted, covering everything from fees and documents to pickups. It shows how you can use the API to track each step, who needs to act, and when, so you can keep deals moving efficiently.

Dive deeper:

After an offer is accepted, a closing takes the car from a deal to a pickup: the commission fee, the Bill of Sale, the funds, the gate pass, the title and the pickup. The API reads every closing your organization is a party to, so your systems and agents can see what is waiting, on whom, and by when. Since 1.5.0.

ScopeGrants
closing:readGET /api/v1/closings and GET /api/v1/closings/{id}, and subscribing to CLOSING.STEP.DUE, CLOSING.STEP.OVERDUE and CLOSING.DOCUMENT.REQUESTED
closing:writeEverything closing:read grants, plus reminding, asking for a document, re-sending a signing link, filling and confirming your side of Deal Setup, marking your own steps done and, in an app Cypress made, starting the commission payment (since 1.11.0)

Neither is a default scope. Both an API key and a Connect Cypress token are accepted.

Read

curl "$BASE/api/v1/closings?status=IN_PROGRESS&yourTurn=true" -H "authorization: Bearer $TOKEN"
curl "$BASE/api/v1/closings/cls_…" -H "authorization: Bearer $TOKEN"

Each closing says which side you are on, SELLER for a car you sold and BUYER for one you bought, and lists its steps:

FieldMeaning
keyFEE, BOS, CONFIRM_FUNDS, GATE_PASS, TITLE, PICKUP. Older closings may also carry BUYER_SIGN, PAYMENT and TITLE_RECEIPT. Key off this, never the step number
deadlineOwnerWho carries the step's deadline: SELLER on a self-serve channel (a direct link or your storefront), where your team does the paperwork, else CYPRESS
turn, yourTurnWho must act now on an open step: SELLER, BUYER or CYPRESS. On the Bill of Sale it follows the signatures: the seller signs first, then the buyer
dueDate, overdueThe step's deadline, set when it unlocks, and whether an open step is past it
lateHours, daysLate, lateOn an open step past its deadline, however little: whole hours late (at least 1), whole days late (0 in the first day) and the words (8 hours late, 2 days late). null when it is not late. Since 1.9.0
dueInHours, dueInDaysWhile the deadline is still ahead: whole hours and whole days left. null when late or with no deadline. Since 1.9.0
documentsThe latest document in each slot, with its signatures
titleShipmentThe title's carrier and tracking number, once the seller sent it

currentStep is the first open step that holds the closing up. requirements lists the documents the closing needs, what fills each, whether Cypress waived it, and whether it is satisfied.

What needs whom first

Since 1.9.0. Cypress works out the deadlines, so your systems and agents do no date math. A deadline in the past is late, however little: a step 8 hours past due reads 8 hours late, never due today.

urgency says it for the current step, with whose turn it is. It is null when nothing is open.

"urgency": {
  "step": "BOS", "turn": "BUYER", "yourTurn": false, "dueDate": "2026-10-28T04:00:00.000Z",
  "lateHours": 8, "daysLate": 0, "late": "8 hours late", "dueInHours": null, "dueInDays": null
}

The list also answers a summary of your open closings:

"summary": {
  "open": 2, "yourTurn": 1, "late": 1,
  "mostOverdue": { "closingId": "cls_…1", "step": "BOS", "turn": "BUYER", "yourTurn": false, "lateHours": 8, "late": "8 hours late", … },
  "yourTurnFirst": { "closingId": "cls_…2", "step": "GATE_PASS", "turn": "SELLER", "yourTurn": true, "dueInHours": 30, … }
}

mostOverdue is the longest past due of all, whoever's turn it is: a closing waiting on the other side can be it, and turn says on whom. yourTurnFirst is the one to act on first yourself. The summary covers your open closings on the side and vehicleId you asked for, on every page, whatever the other filters. On MCP, list_closings keeps urgency and summary in its summary view, where the steps are left out.

sort=urgency puts your turn first, then the longest past due, then the soonest due, to the millisecond. A step with no deadline comes after them, and a closing with nothing open comes last. Left out, the list stays newest first.

Deal Setup

dealSetup says where Deal Setup stands, so you see who the Bill of Sale is waiting on without another call. It is null on a closing that predates Deal Setup. Since 1.9.0.

"dealSetup": {
  "seller": { "confirmed": true, "confirmedAt": "2026-10-20T15:02:11.000Z", "blanks": [] },
  "buyer": { "confirmed": false, "confirmedAt": null, "blanks": ["titleAddress", "custom.buyer.stock_number"] }
}

blanks names the facts a side still leaves empty, never their values:

SideNames
SellerpickupLocation; bankAccount (when no lender is named either); poNumber when the seller's organization needs a PO on every deal (in the seller's own read only: it is the seller's billing term with Cypress)
BuyertitleAddress, transporter
Bothcustom.seller.<key> or custom.buyer.<key> for each custom field the organization asks at Deal Setup

A side may confirm with blanks left, except a required PO. The full Deal Setup, with the choices and their values, is GET /api/v1/closings/{id}/deal-setup.

What each side sees

  • Signing links are your own side's. Each signature names its party and status; only your side's carry signingLink and email, on the closing and on get_closing_signatures alike (since 1.11.0 the seller no longer gets the buyer's there). An outside signer is emailed by Cypress's signing service and has neither. Whoever holds a link can sign in that party's place, so only Cypress staff see every party's. resend_signing_link sends any party theirs at the address on file; a new address for another party's signer (correct_signer_email) is Cypress staff's to set.
  • The commission is the seller's. The FEE step, and any requirement on it, is in the seller's view only.
  • No money detail, and the other party by name only. No payment instructions, bank or lender accounts, amounts paid or references. The seller's commission invoice is a call of its own (get_commission_invoice; paying it is below). price is the agreed price. counterparty names the other company (its side and its organization's name, as the closing page in the app shows it): it is your own trade, and the Bill of Sale names both. Never a person or a contact detail.
  • Only your closings. Another company's closing answers 404, as one that does not exist.

Move a closing along

Three acts, none of which changes a step: they tell the people who must act. Each takes ?dryRun=true to see what would happen and an Idempotency-Key for safe retries, is recorded in your audit log with the key or app that did it, and can be repeated only after a while (a 409 carries retryAfter).

# Remind whoever's turn the Bill of Sale is
curl -X POST "$BASE/api/v1/closings/cls_…/steps/BOS/remind" -H "authorization: Bearer $TOKEN"

# Ask for a document a requirement still lacks
curl -X POST "$BASE/api/v1/closings/cls_…/document-requests" \
  -H "authorization: Bearer $TOKEN" -H "content-type: application/json" \
  -d '{ "requirementId": "req_…" }'

# Email a signer their signing link again
curl -X POST "$BASE/api/v1/closings/cls_…/documents/doc_…/signatures/buyer/resend" \
  -H "authorization: Bearer $TOKEN"
ActWho hears itAgain after
RemindWhoever's turn the step is: the seller's fleet managers and admins (with your account manager copied), the buyer, or CypressAn hour
Ask for a documentWhoever does the requirement's step: the seller for most papers, the buyer for the pickup's. Both sides' systems also get CLOSING.DOCUMENT.REQUESTEDAn hour
Re-send a signing linkThe signer, by email from Cypress's signing service. The seller may re-send to anyone on the document; a buyer to its own signers. Signing goes in order, so a link is not re-sent before its signer's turnTen minutes

Mark your own steps

Your side's own steps can be marked done through the API. The same step action the app runs does the work, with the same checks, notifications and events:

SideStepActionFields
SellerCONFIRM_FUNDSconfirm_fundsmethod (ACH or CHECK), reference, carrier, note
SellerGATE_PASSissue_gate_passaddress, city, state, zip
SellerTITLEsubmit_title_trackingcarrier, trackingNumber, titleHolder, titleHolderType, titleHolderAddress
SellerTITLEconfirm_check_clearednone (check payments only)
BuyerPICKUPconfirm_pickupnone; it completes the sale
curl -X POST "$BASE/api/v1/closings/cls_…/steps/GATE_PASS/actions" \
  -H "authorization: Bearer $TOKEN" -H "content-type: application/json" \
  -d '{ "action": "issue_gate_pass", "address": "1 Lot Rd", "city": "Austin", "state": "TX", "zip": "78701" }'

The answer is the step's and the closing's status afterwards. The other side's step is 403 POLICY_DENIED; a step that is not open yet, or a requirement still missing, is 409. Cypress's own actions, such as approving a Bill of Sale, are never offered.

Pay the commission, in Cypress's own app

The seller pays Cypress's commission once both sides have confirmed Deal Setup. In Cypress that is Pay Now. Since 1.11.0 an app Cypress made (Grove) opens the same payment on its own screen with start_commission_payment:

curl -X POST "$BASE/api/v1/closings/cls_…/commission-invoice/checkout" \
  -H "authorization: Bearer $TOKEN" -H "content-type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" -d '{ "saveCard": true }'

It answers clientSecret and publishableKey. The app mounts Stripe's embedded Checkout with them, and the person types the card into Stripe's own form: card details reach Stripe only, never Cypress or the app. Once it is paid the FEE step completes and CLOSING.STEP.COMPLETED is sent, as from Pay Now. saveCard keeps the card for the organization's next commissions, charged by itself when Deal Setup is confirmed.

Paying stays a person's: an agent, an API key and any app Cypress did not make are refused (403 POLICY_DENIED), and so is the buyer. A commission not invoiced yet, already paid, or on the organization's cycle bill is 409 CONFLICT.

Report a problem, with pictures

POST /api/v1/closings/{id}/incidents reports a problem on a closing in progress (report_closing_incident), and POST /api/v1/closings/{id}/incidents/{incidentId}/messages adds to its thread (message_closing_incident). Since 1.9.0 both take up to five pictures in photos, each sent the way add_vehicle_photo takes one: contentBase64, contentType (image/jpeg, image/png, image/webp or image/heic, at most 10 MB) and an optional fileName.

curl -X POST "$BASE/api/v1/closings/cls_…/incidents" \
  -H "authorization: Bearer $TOKEN" -H "content-type: application/json" \
  -d '{ "stepKey": "PICKUP", "category": "VEHICLE_DAMAGE", "description": "Dent on the tailgate",
        "photos": [{ "contentBase64": "iVBORw0…", "contentType": "image/jpeg", "fileName": "tailgate.jpg" }] }'

GET /api/v1/closings/{id}/incidents answers each message's pictures with their name, size, mimeType and a url valid for an hour. A refused call keeps none of the pictures it sent.

Deadlines as events

Three events follow a closing while it waits on someone:

EventWhen
CLOSING.STEP.DUEAn open step's deadline is less than 12 hours away
CLOSING.STEP.OVERDUEAn open step's deadline has passed
CLOSING.DOCUMENT.REQUESTEDSomeone asked for a document a requirement still lacks; the body names the requirement, who it is askedOf, who requestedBy, and yourDocument
  • Both sides hear them. Unlike the other CLOSING.* events, which go to the seller, these go to the seller and to the buyer, each in its own organization. The commission step's go to the seller only.
  • They say whose turn it is. The body is the step as your side sees it:
{
  "closingId": "cls_…",
  "side": "BUYER",
  "vehicleId": "clx…",
  "step": {
    "key": "PICKUP", "number": 6, "label": "Vehicle pickup",
    "deadlineOwner": "CYPRESS", "turn": "BUYER", "yourTurn": true,
    "dueDate": "2026-10-16T09:00:00.000Z"
  }
}
  • Once each. A step is told once per deadline; a deadline that moves is told again.

What happened, as events

The older CLOSING.* events (CLOSING.STARTED, CLOSING.STEP.COMPLETED, CLOSING.DOCUMENT.SIGNED, CLOSING.PAYMENT.RECEIVED, CLOSING.CANCELLED, CLOSING.COMPLETED) go to the seller. Both sides get CLOSING.UPDATED, each in its own organization with its side, with a change that says what happened: STEP_COMPLETED, DOCUMENT_SIGNED, COMPLETED, DEAL_SETUP_UPDATED, DEAL_SETUP_CONFIRMED, INCIDENT_REPORTED, INCIDENT_UPDATED, ROLLED_BACK and the others listed in the event catalog. The commission step, and an incident that is not the buyer's, are told to the seller alone. All of them are in GET /api/v1/events for 30 days.

Since 1.9.0:

  • every path that completes a step tells it: a person's step action, the last signature, the commission paid, the title accepted, Cypress confirming a step, an off-platform attestation (with offPlatform: true);
  • a document signed in ink and filed as the signed copy tells DOCUMENT_SIGNED. The gate pass is signed in two places: the seller releases it and the transporter signs at the yard, then the seller files the countersigned pass with wetFor: ["seller", "external"] (external is the transporter). The pickup waits for the transporter's signature;
  • a new message on an incident tells INCIDENT_UPDATED with its messageId (never its words);
  • ROLLED_BACK names the step it went back to, rolledBackTo;
  • DEAL_SETUP_UPDATED names reopenSuggestedTo when a change made paper on a completed step wrong: a Cypress Manager decides whether that step is reopened.
  • Opt-in. Only a Cypress-dialect subscription that lists the event gets it, and subscribing needs closing:read. The envelope's href is /api/v1/closings/{id}.