A renter books a boat on RentalTide. Before they pay, they are offered your coverage, priced by you, presented in our interface. If they take it, they answer your underwriting questions inside our checkout, pay once, and you bind the policy. Afterwards they see the policy and its documents on their booking page, and anyone who declined gets a second chance to buy.
You are not building a web app. You expose a handful of HTTPS endpoints. We do the rest: the coverage card, the question rendering, mobile and dark mode, localisation, the payment, the tax, the receipt, the order summary, refunds, the follow-up offer, document display, and the operator-facing reporting.
Your offering works on sandbox locations from the moment you create it, with no approval step — quoting, questions, requoting, binding, the receipt, the Next Steps page. Approval is only needed to sell to real renters.
The renter's journey
| Step | Where | What happens |
|---|---|---|
| 1 | Booking flow | Renter picks a boat and a time. We call your /quote. Your coverage appears as a card with a real price. |
| 2 | Booking flow | They select it. It joins the order as an estimate. |
| 3 | Checkout | We render your questions in our UI. They answer. |
| 4 | Checkout | We call /requote with the answers and get a final price. |
| 5 | Checkout | They pay once. Your premium is part of that single card charge. |
| 6 | After payment | We call /bind. You return a policy number and a document. |
| 7 | Next Steps page | We show the policy and document, and offer the coverage again to anyone who declined. |
The renter never leaves RentalTide and never sees a third-party form.
Getting set up
1. Create a partner account
partners.rentaltide.com. Free, active immediately, no approval queue for the account itself.
2. Connect a payout account
Payouts → Connect with Stripe. Standard Stripe Express onboarding: bank details, business verification. Do this early — your coverage offering cannot be approved for production without it, and the account you connect here is attached to your offering automatically.
We can pay into the US, UK, EEA, Canada and Switzerland. The country list only shows those, because being told after you have earned money that we cannot send it to you is not a conversation anyone should have.
3. Scaffold your endpoints
The scaffold is built on @rentaltide/underwriter, so what you edit is underwriting and nothing else:
The SDK handles the parts that are easy to get quietly wrong:
- The signature — timing-safe, timestamp-bounded, and taken over the raw bytes.
JSON.stringifyof a parsed body does not round-trip byte for byte. - Idempotent binds — it remembers the policy against its
quoteId, so our retry cannot make you write a second one. Memory by default; pass astorebefore you sell a real policy. - The rules below, checked where your handler returns rather than becoming a card that silently never renders.
It runs anywhere: rentaltide.handler() for Node, rentaltide.fetch for Workers, Hono or a Next route handler.
Not using Node? Everything in the SDK is the contract below, implemented once. Implement it directly and coverage test judges it exactly the same.
4. Register the offering
It reads rentaltide.coverage.json, registers the offering, writes the id back, and saves your signing secret for the CLI. Put that secret in your own environment too — export SIGNING_SECRET=whsec_… — and restart your server.
No endpoint returns it again. coverage create saves a copy to ~/.rentaltide/credentials.json before printing it, so a closed terminal does not lose it — but only on that machine.
The same call over HTTP, if you would rather not use the CLI:
Then rentaltide coverage link and rentaltide coverage secret to point the project at it.
baseUrlmust be https. We sign every call to it and send renter identity at bind; over plaintext a signature proves nothing to anyone watching. It can point anywhere while you build — see step 6.supportedCountriesis where you are licensed to write. Empty means nowhere, not everywhere.quoteTimeoutMsis capped at 5000 and runs while a renter watches a page repaint.
5. Prove the contract
Signs with your real secret — same headers, same {timestamp}.{body} payload, same deadlines — so your signature verification is proven before a booking exists. It checks the rules that fail quietly:
Each of those is something that type-checks, answers 200, and breaks a renter. It exits non-zero, so keep it in your CI.
Individual calls are commands too, for while you are still writing it:
6. Test in sandbox
Deploy your endpoints somewhere we can reach, point the offering at it, and run the whole renter flow against a real test marina:
That repoints the offering's base URL while you work and restores it when you stop. Then open the sandbox booking page, pick a boat, and watch your card appear, your questions render in the checkout, and your policy bind after a test card is charged.
Nothing is transferred in sandbox.
7. Submit for review
Or POST https://v3.api.rentaltide.com/developer/coverage-providers/{id}/submit.
We check you are actually ready before approving — a payout account, at least one licensed country, a signing secret — and tell you exactly what is missing if not.
8. Approved
A merchant enables your offering for their locations — one switch on their Coverages page — and it appears in their renters' checkout. Registering with us is not permission to appear in somebody's checkout; only the merchant grants that, and they can withdraw it at any time.
You have a switch of your own. Accepting quotes is on from the moment you create the offering; turning it off stops every call we would make to you, in sandbox and production, without unwinding anything you have already bound. It is the control to use when your service is down.
Changing where data or money goes — your baseUrl, payout account, licensed countries or currencies — sends the offering back for review, and it keeps working in sandbox meanwhile. Name, support address, quote timeout and the on/off switch change freely.
Calling your own endpoints
Every call below can be fired at your server by the CLI — rentaltide coverage quote | schema | requote | bind | reschedule | cancel — signed exactly the way we sign it. Each prints the signed request, the status, the timing and the body, and exits non-zero on anything but a 200. --data '{…}' replaces the body outright; --url overrides where it goes.
The contract
We always call you. The one exception is the messaging endpoint at the end.
Each call is shown twice: what you write with @rentaltide/underwriter, and the wire format underneath for anyone implementing it directly. The two are the same contract — the SDK is it, written once.
Every request we send carries:
Verify both. Reject stale timestamps.
POST /quote
As soon as the renter has chosen a boat and a time. No renter identity is sent — at this point they have bought nothing and agreed to nothing, and most visitors never will.
On the wire:
Everything above the flat fields is resolved by us, not taken from the browser: the asset facts are re-read from the listing by id, and place.country is the same resolution the licensing gate used. A renter cannot shop for a cheaper band by editing a request.
The flat fields at the bottom are the original contract and are still sent, so a provider written against the first version of this page keeps working untouched.
country is resolved for every location. region is not: it comes from a value a merchant set deliberately or from the state on their Stripe account, and it is never parsed out of the address line — a street address is free text, and a tax filing is the last place to start guessing.
So a location whose state we do not hold sends region: null. If you rate or remit on the subdivision (surplus lines tax is remitted to the state a policy is written in), decline that quote rather than assume one. We would rather lose the sale than have you file in the wrong state.
Tell us which of your locations come through without a region and we will chase the merchants for them.
signals carries how far ahead they booked, what hour they are starting, how long they have been deciding, roughly where the request came from. It carries no IP address, no user agent and no email — at quote time the renter has agreed to nothing, and most visitors never buy. Identity arrives once, at /bind, where there is a contract to justify it.
Response:
depositEffect is one of none, waive, reduce_to, reduce_by, reduce_by_percent. Anything but none or waive must carry a depositEffectValue: a reducing effect with no number renders to the renter as a saving and then changes nothing at hold time, so we reject it.
Miss it and your card does not render; the booking proceeds without it. No renter waits on an insurance quote to see a checkout button.
A premium must be a JSON number. "44.50" is rejected.
POST /schema
We ask once per questionSchemaVersion and cache it, so checkout never waits on you to draw a form.
On the wire:
Supported types: text, number, date, checkbox, dropdown, multiselect, address, and notice for a disclosure block.
- Schema, not markup. No HTML, no scripts. A field type we cannot draw is dropped rather than rendered half-finished.
- Pinned per quote. Changing your schema never alters a quote in flight.
- Patterns are bounded. A regex with a nested quantifier (
(a+)+) is refused: it runs against renter input on our servers. - At most 12 fields. Each one is friction at the moment somebody is deciding to pay, and we measure checkout completion with and without your coverage selected.
POST /requote
When the renter submits your questions.
On the wire:
decision is accepted or declined. A decline is a normal outcome: we remove the coverage and the renter completes their booking without it.
Answers are validated against your pinned schema on our side first, so minAgeYears and pattern are enforced before they reach you. Conditional fields the renter never saw are not sent.
POST /bind
Only after payment has succeeded. The first and only call carrying renter identity.
The SDK remembers the policy against its quoteId, so our retry returns the same one rather than calling your handler twice. In production give createUnderwriter a store, or a restart forgets and the next retry issues a second policy.
On the wire:
operators is everyone who signed a waiver for this booking, not just whoever paid, because a policy is written against the people who will be on the water. Names and waiver status only: you already have the buyer's contact details, and the other operators never agreed to theirs being passed on. It is null when the merchant only collected one name.
location is where the business is; launchLocation is where the boat goes in, when the merchant runs delivery or a separate ramp. They are regularly not the same place, and location.region is the same resolution your quote was rated against.
nameOnCard is here because it is the field that disagrees with the booking name in a fraud case. The card number never is, and never will be — brand and last four are the most a policy file needs and the most PCI lets us pass on.
You get whether a waiver was required, whether it was signed, when, and which template. You do not get the answers, which routinely carry health information — a different category of data with a different legal basis. If your underwriting needs a medical disclosure, ask for it in your own /schema, where the renter can see what they are answering and why.
We retry on timeout or 5xx. Returning the same policy for the same quote must not write a second one. documentUrl must be https.
POST /reschedule
When a booking's dates move after the policy is bound. Rentals get rescheduled, extended at the dock, and corrected on the order page, so the window you priced is not always the window the renter turns up for.
On the wire:
decision is accepted or declined, with an optional reason. A bare 200 with no decision is read as acceptance.
This is not a requote. The renter has already paid, and there is no path to charge them more without asking them again, so a new premium here is not something we can act on. The question is only whether the policy still stands. A decline is raised to the operator as a customer who paid and is no longer covered.
We detect the change by comparing the window you were bound against with the booking's current dates, so every way a date can move is covered — not only the renter-facing reschedule. Keyed on the policy plus the new window, so a retry after a timeout is the same amendment rather than a second one.
POST /cancel
On the wire:
Matching your products to their boats
productCode on a quote is which of your products covers this listing. Left alone it is whatever the offering was created with, which means a jet ski and a 24' pontoon arrive at you indistinguishable.
The screen where an operator picks belongs in your app, not ours: it is your product list, in your words, and you are the one who knows which product fits a boat. Build it as an app with a settings page, then write the operator's choices back:
GET the same path to read them back. A productCode of null or "" clears the mapping and that listing falls back to the offering's own code. metadata is yours — we store it and hand it back, and never look inside it.
From then on every /quote for that listing carries the code the operator picked, already resolved.
The provider is derived from the app's owner, never from the request: an app can only write mappings for coverage offerings belonging to the same partner, only for the tenant it is installed on, and only for listings that belong to that tenant. An unknown listing comes back in unknownInventoryIds rather than being written.
It also requires the merchant to have your offering enabled. Registering with us is not permission to write against somebody's account.
Without an app, you can still map on your side: every quote carries asset.inventoryId, which is stable per listing, along with the category, capacity and seating capacity.
Estimate, then final price
Between the card and the questions the renter is looking at an estimate.
| Outcome | What we do |
|---|---|
| Same as the estimate | Proceed silently |
| Lower | Proceed, show the new price |
| Higher | Stop. The renter must explicitly accept the new price before paying |
| Declined | Remove the coverage, let the booking continue |
We never charge a renter more than the price they were shown without asking, and we compare against our own record of what was displayed, not a number sent back by the browser.
If final prices routinely differ from estimates, the estimate is doing harm. Worth agreeing a tolerance up front and measuring it.
Money
- One payment. The renter pays the rental and your premium together on one card charge. No second checkout, no second receipt.
- We collect, then pay you. Your premium is held back from the operator's settlement and transferred to your Stripe account, minus our share. The transfer happens after you confirm the policy, so we never pay for a policy that does not exist.
- Refunds. A cancelled booking cancels the policy and reverses the transfer.
- Sandbox moves no money. Everything else runs; the transfer does not.
Talking to the renter
You have real reasons to write to the customer: here is your policy, here is your document, your claim was settled. We will not hand over an address, so instead you ask us to send it:
Categories: policy_issued, policy_document, policy_cancelled, claim_update. There is no custom.
- We resolve the recipient from the policy. You never name an address.
- Plain text only. It is escaped and wrapped in our template, attributed to you, sent from our authenticated domain with your support address as reply-to.
- One of each category per policy. A retried webhook is a no-op, not a second email.
- The merchant must have your offering enabled. If they turn it off, messages stop immediately.
When something fails
Coverage is an add-on. Nothing about it may block a rental.
| Failure | What happens |
|---|---|
/quote times out or errors | Your card does not render. Booking proceeds. |
/requote fails | Coverage is dropped from the order. Booking proceeds. |
/bind fails after payment | The booking stands. It is flagged to the operator within 30 minutes, and the renter's page says the policy has not issued yet. |
| Your service is down | The same, at every step. |
If you have a status endpoint or a maintenance signal, tell us — we would rather stop offering your product cleanly than time out on every page load. Switching Accepting quotes off does the same thing immediately.
When we do not call you at all
Worth knowing before you go looking for a bug on your side. We resolve each location's country from what the merchant has stored and fail closed: no country, no quote request, because selling a policy outside where you are licensed is worse than selling none. The same silence applies when the settlement currency is not one you price in, when the merchant has not enabled you, when your offering is switched off, and — for a production location — until you are approved.
What we need, and what you need
From you
- Sandbox endpoints for
/quote,/schema,/requote,/bind,/rescheduleand/cancel - One product definition: name, description, terms, coverage limit, and what it does to the security deposit
- Your question schema
- The countries and currencies you are licensed to write in
- Stripe Connect onboarding, from the Payouts page
From us
- A partner account and your signing secret
- A sandbox marina with test inventory, so you can run the full renter flow
- This document as the contract between us
- A data-sharing agreement naming the controller for the renter data sent at bind, before you go live
Things we answer per integration
A merchant can run their own damage coverage and yours at the same time. Both appear in the same list, and the merchant decides which products are active per location. You are not competing for a single slot.
Date changes reach you within the hour. A booking that moves after bind calls your /reschedule. If you would rather receive it immediately or not at all, say so during review.
Cancellation. A voided or refunded coverage sale calls your /cancel and reverses the transfer. The reversal is attempted even when your /cancel fails: the money is ours to recover either way, and a disagreement about a cancellation is a conversation, not a reason to leave the funds out.
Claims handling and premium tax are yours to state. Who takes a claim, and whether a region charges insurance premium tax rather than sales tax, differ by underwriter and by country. Tell us in your submission and we will render it at the point of sale rather than guess a default.
Anything else: partners@rentaltide.com.

