RentalTideRentalTideRentalTideDocs
Dashboard›

Embedded insurance

Offer your coverage inside a RentalTide checkout — quote before the renter pays, ask your underwriting questions in our UI, take one payment, and bind the policy.

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.

Build the whole thing before anyone reviews you

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

StepWhereWhat happens
1Booking flowRenter picks a boat and a time. We call your /quote. Your coverage appears as a card with a real price.
2Booking flowThey select it. It joins the order as an estimate.
3CheckoutWe render your questions in our UI. They answer.
4CheckoutWe call /requote with the answers and get a final price.
5CheckoutThey pay once. Your premium is part of that single card charge.
6After paymentWe call /bind. You return a policy number and a document.
7Next Steps pageWe 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

Bash

The scaffold is built on @rentaltide/underwriter, so what you edit is underwriting and nothing else:

JavaScript

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.stringify of 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 a store before 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

Bash

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.

Your signing secret is shown exactly once

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:

HTTP
POST https://v3.api.rentaltide.com/developer/coverage-providers
Authorization: Bearer <your partner token>
Content-Type: application/json

{
  "name": "Example Marine Cover",
  "baseUrl": "https://api.example-underwriter.com/rentaltide",
  "supportEmail": "help@example-underwriter.com",
  "supportedCountries": ["US"],
  "supportedCurrencies": ["USD"],
  "quoteTimeoutMs": 800
}

Then rentaltide coverage link and rentaltide coverage secret to point the project at it.

  • baseUrl must 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.
  • supportedCountries is where you are licensed to write. Empty means nowhere, not everywhere.
  • quoteTimeoutMs is capped at 5000 and runs while a renter watches a page repaint.

5. Prove the contract

Bash

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:

✓ Refuses an unsigned request
✓ Quote answers inside 800ms                 2ms
✓ Premium is a JSON number
✓ Deposit effect is coherent
✓ A decline is a 200 with decision: declined
✓ Bind is idempotent on quoteId
✓ Policy document is https

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:

Bash

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:

Bash

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

Bash

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 things after approval

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:

X-RentalTide-Signature: v1=<hex HMAC-SHA256 of "{timestamp}.{raw body}">
X-RentalTide-Timestamp: <ISO 8601>

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.

JavaScript

On the wire:

JSON

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.

`place.region` can be null, and you should decline when it is

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 are circumstance, never identity

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:

JSON

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.

Budget: 800ms

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.

JavaScript

On the wire:

JSON

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.

JavaScript

On the wire:

JSON
JSON

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.

JavaScript

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:

JSON

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.

Waiver status, not waiver answers

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.

JSON
Idempotent on quoteId

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.

JavaScript

On the wire:

JSON
JSON

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

JavaScript

On the wire:

JSON

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:

HTTP
PUT https://v3.api.rentaltide.com/marketplace/apps/{clientId}/coverage-mappings
Authorization: Bearer <the installed app's token>

{
  "mappings": [
    { "inventoryId": "34b600fb-…", "productCode": "pwc-standard" },
    { "inventoryId": "9c21af03-…", "productCode": "pontoon-liability",
      "metadata": { "ratingTier": "B", "hullValueBand": "40-60k" } }
  ]
}

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.

What the write is scoped to

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.

OutcomeWhat we do
Same as the estimateProceed silently
LowerProceed, show the new price
HigherStop. The renter must explicitly accept the new price before paying
DeclinedRemove 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:

HTTP
POST https://v3.api.rentaltide.com/coverage-provider/messages
X-RentalTide-Provider: your-slug
X-RentalTide-Signature: v1=...
X-RentalTide-Timestamp: ...

{
  "policyId": "POL-88213",
  "category": "policy_document",
  "subject": "Your coverage documents",
  "body": "Plain text. Paragraphs separated by blank lines.",
  "documentUrl": "https://.../policy/POL-88213.pdf",
  "reference": "your-own-id-for-retries"
}

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.

FailureWhat happens
/quote times out or errorsYour card does not render. Booking proceeds.
/requote failsCoverage is dropped from the order. Booking proceeds.
/bind fails after paymentThe 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 downThe 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

  1. Sandbox endpoints for /quote, /schema, /requote, /bind, /reschedule and /cancel
  2. One product definition: name, description, terms, coverage limit, and what it does to the security deposit
  3. Your question schema
  4. The countries and currencies you are licensed to write in
  5. Stripe Connect onboarding, from the Payouts page

From us

  1. A partner account and your signing secret
  2. A sandbox marina with test inventory, so you can run the full renter flow
  3. This document as the contract between us
  4. 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.

Was this page helpful?
Need help? Contact Support.See what’s new. Check out changelog.Questions? Book a video chat.
Ask AI
Responses are generated using AI and may contain mistakes.
Ask questions about RentalTide and get help with your integration.