Carousel docs
Tenant screening

Screening products

Tenant screeningThese docsCredit, criminal, eviction, income and ID for rental applicants.
Commercial screeningComing soonCredit, court records, ID and bank-verified income for commercial tenants and their guarantors.
Employment screeningComing soonID, police checks and Québec penal searches for candidates, and credit where the role calls for it.
Regular screeningComing soonBackground and identity checks for staff, members or contractors.
Developer guides · Carousel API

Show reports in your platform

Your users read a Carousel report inside your own product, with its PDF. They don’t need a Carousel login, and your system has nothing to store but ids.

For platforms whose users don’t sign in to Carousel — a leasing tool, a CRM, a property-management app. If your users already have Carousel logins, show them portalUrl instead: see Send and follow screenings.

See it working

A sample CRM that sends, tracks, then shows Carousel’s real report inside it — click through exactly what your users get, then the code behind it: the full example, in Integrate your CRM.

What your user sees

Your platform’s page, with Carousel’s read-only report in a frame inside it your-platform.com/applications/APP-88231 Your platform Applications PropertiesClientsSettings Application APP-88231 Unit 1203 · 88 Queens Quay W, Toronto CAROUSEL · READ-ONLY MT 88 Queens Quay W › 1203 · $2,450/mo Maya Tremblay OverviewChecksPDF Identity verificationDriver’s licence · selfie matchesClear Credit checkScore 742 · no collectionsClear Court & evictionNo records foundClear Financial verification2 accounts · income verifiedClear Download the PDF
Your pageYour platform, your navigation — and Carousel’s report in a frame inside it: read-only, with every check, its documents and its PDF.

How it works

How a report opens: your user, your server and Carousel, in seven steps Your user’s browser Your server Carousel your page + the frameholds your API keyapp.oncarousel.com Jane opens the report in your app Your rule: may Jane see it?only her own applications POST /screenings/{id}/view-links a one-time link · 2 minutes <iframe src="…/embed/r/…"> the frame opens the link: it becomes a 30-minute view the report, read-only — Overview, Checks and the PDF 1 2 3 4 5 6 7 Your key stays on your server · the link only works in the frame · the report stays with Carousel
The flowEach time a user opens a report, in seven moves — steps 4 and 5 of Integrate your CRM, in detail. Your server decides who may see what; Carousel shows the report.

One Carousel account is enough for a whole platform: its key sends every screening and makes every link, and your own login decides which of your users sees which application. Carousel doesn’t keep your customers apart — your server does: a key that shows reports can make a link for any report the key reaches, so check which customer an application belongs to before each link — or give each customer a key limited to their own workspace, which reaches no one else’s reports. Integrate your CRM says what else they share.

Set it up once

1

Make a key that can show reports

Account menu → Developers → Keys, with Show reports in my app on: a test key (cp_test_…) to build, a live key for real reports. Keep it on your server. Choose which workspaces it reaches as you make it: every workspace you can use, or only the ones you tick — a key per customer, limited to that customer’s workspace, shows no one else’s reports. Its holder can open any report the key reaches, documents included, so guard it like a results key; it just can’t call the results API. A key with Read applicant results can make links too. If you’re a developer on someone else’s account, a live key with this option needs the owner to switch on Can see applicant data for you (Developers → Access); test keys don’t. Switching it off later doesn’t stop keys already made: revoke those under Keys.

2

Allow your platform’s addresses

Account menu → Developers → Allowed sites: add each exact address your app runs on — https://app.your-platform.com, every subdomain or custom domain your customers use, and http://localhost:3000 (your port) while you build — localhost shows test reports only; a live one needs your own https site. Paste a page’s address and its site is kept. No wildcards; up to 25 — if your customers use more domains, show reports from pages on one domain of yours, like reports.your-platform.com. A site works from the next view; until it’s added, the frame there says “This report can’t be shown on this site”.

3

Keep Carousel’s screening id with your record

One per applicant. Send your own id as externalId on every send: GET /screenings?externalId=<your id> returns each applicant you sent with it — see Send and follow screenings.

From your server, once you’ve checked that this user may see this application:

curl -X POST https://app.oncarousel.com/api/v1/screenings/<id>/view-links \
  -H "Authorization: Bearer $CAROUSEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "viewer": "jane@agency.example" }'

201:

{
  "url": "https://app.oncarousel.com/embed/r/kV9q2xR7mPz4tLwB8nYc3hJd6sFg1aEu5oKiQvXbN0e",
  "expiresAt": "2026-10-06T15:02:00.000Z"
}

viewer is your user, as you name them — an email or your own id, up to 200 characters. Carousel records it with the link and every view; it doesn’t check it. A key can make 120 links a minute, apart from its 120 requests.

Put it in a frame straight away

<iframe src="https://app.oncarousel.com/embed/r/kV9q2xR7…"
        title="Screening report"
        style="width:100%; height:calc(100vh - 64px); border:0"></iframe>

The same thing in a Node server, from your user’s click to the page:

app.get('/applications/:id/report', requireLogin, async (req, res) => {
  const record = await db.applications.find(req.params.id);
  // Your rule: only the user's own applications.
  if (!record || record.agencyId !== req.user.agencyId) return res.sendStatus(404);

  const answer = await fetch(
    `https://app.oncarousel.com/api/v1/screenings/${record.carouselScreeningId}/view-links`,
    {
      method: 'POST',
      headers: { Authorization: `Bearer ${process.env.CAROUSEL_API_KEY}`, 'Content-Type': 'application/json' },
      body: JSON.stringify({ viewer: req.user.email }),
    },
  );
  if (!answer.ok) {
    const { error } = await answer.json(); // { code, message, hint }
    return res.status(502).render('report-unavailable', { code: error.code });
  }
  const { url } = await answer.json();
  res.set('Cache-Control', 'no-store'); // the link works once: never serve this page from a cache
  res.render('report', { frameUrl: url }); // <iframe src="{{frameUrl}}">
});

Your page

  • Allow the frame in your own CSP. If your page sends a Content-Security-Policy, add frame-src https://app.oncarousel.com.
  • Don’t sandbox the iframe. The report runs scripts and downloads its PDF; a sandbox attribute breaks both.
  • Load each link once. The first load spends it, so a frame that mounts twice — a re-render that resets src, React’s StrictMode in development — shows “This view has ended”. Ask for a new link instead of reusing one.
  • Give it room. The report scrolls inside the frame. It shows two columns from 900 px wide and stacks below that; a height of the window less your header works well.
  • Native apps: open an HTTPS page of yours, on your allowed sites, that holds the frame — not the link itself.

One link, two clocks

A link lasts two minutes and works once; the view it opens lasts thirty minutes THE LINK THE VIEW IT OPENS 2 minutesworks once 30 minutestabs, documents and the PDF, for this one report Endedmake a new link the frame’s first load spends the link not to scale
TimingMake a link each time a user opens a report — never ahead of time, and never to keep.
  • The link works once, within 2 minutes: long enough for your page to load the frame, and useless after.
  • The view it opens lasts 30 minutes. Switching tabs, opening documents and downloading the PDF all happen inside it.
  • When it ends, the frame shows “This view has ended” and tells your page, so you can offer a fresh one:
window.addEventListener('message', (event) => {
  if (event.origin !== 'https://app.oncarousel.com') return;
  if (event.data?.type === 'carousel.view_ended') showReopenButton(); // asks your server for a new link
});

The message comes when an open view’s 30 minutes run out. A link that was already used, or is over 2 minutes old, shows the same words without it — so keep a way to reopen on your page as well.

Build it with a test key

A test key’s link opens Carousel’s sample report — a fictional applicant under a Test mode bar — through exactly the same steps: one use, 2 minutes, 30 minutes, the PDF, the ending. Send a test screening, ask for its link, and frame it from localhost once that address is in your allowed sites. No real applicant’s data is read, nothing is charged or logged, and no screening.viewed is sent.

What’s in the frame

ShownNot shown
The Overview and every check’s resultsYour team’s notes and who it’s assigned to
The stored documents: ID, court files, uploadsReminders, cancelling, adding people
The PDF: preview, print view and download — without the “Also available on Carousel” list of checks this screening didn’t includeThe other applicants on the file — each has their own link
Where the rental is and its rentYour workspaces, costs and settings

Nothing in the frame leads into Carousel. Before the applicant finishes, the report says what’s still waiting (“Nothing back yet — 3 checks waiting on the applicant”). A Québec court file that hasn’t been opened shows its number only.

Who viewed it

Each time the report loads in the frame — the first load, a reload, the print view, every PDF download — your webhook gets a screening.viewed, saying whose it was, once the applicant has opened their link. One view can send several:

{
  "type": "screening.viewed",
  "data": {
    "screening": { "id": "6f0c…", "externalId": "APP-88231", "portalUrl": "…" },
    "via": "embed",
    "viewer": "jane@agency.example"
  }
}

via is embed for a view through a link, carousel for your team in Carousel. See Events and webhooks.

How it stays safe

RuleWhat it means
One use, 2 minutesA copied or forwarded link opens nothing. So does one opened by an email’s link scanner.
One reportA view opens the screening it was made for, one the key reaches, and nothing else.
Only in your platformThe report shows only inside the sites your account allowed — the key’s account’s, even for a report in a workspace another account shared with you — and never as a page of its own — not in a new tab, not from an email.
Read-onlyNothing in the frame can change the screening.
LoggedEvery link is recorded with your viewer, and so is every view, document and PDF once the applicant has opened their link — before that, the report has nothing to read.
RevocableRevoking the key stops all its links and views at once: nothing more loads, and no document or PDF opens. So does the key’s owner losing the screening’s workspace, when a share ends for example: the view checks the key’s reach every time it opens something. A report already on screen stays there until the frame reloads or its 30 minutes end.
Nothing to storeThe results stay with Carousel; your system keeps one id per applicant.

When something goes wrong

Asking for a link:

HTTPCodeWhat to do
400invalid_requestSend viewer, 1 to 200 characters, and nothing else.
401unauthorizedSend Authorization: Bearer <key>.
403view_links_not_allowedMake the key with Show reports in my app on.
404not_foundUse the id of a screening this key reaches (a test id with a test key), once its invitation went out.
429rate_limitedOver 120 links, or 120 requests of any kind, a minute for this key. Wait the seconds in Retry-After.
503busyCarousel is briefly at capacity: retry after Retry-After.

In the frame:

Your user seesWhyWhat to do
This view has endedIts 30 minutes are up, the link was used already or is over 2 minutes old, or the key was revoked.Make a new link.
This report opens inside your platformThe link or view was opened as a page of its own.Open it in your frame.
Too many requestsOne view loaded over 60 times in 10 minutes. (Over 300 documents or 6 PDFs in 10 minutes, the file just doesn’t open.)Make a new link, or wait for the 10 minutes to pass.
“This report can’t be shown on this site”, or the browser’s “refused to connect”Your page’s address isn’t in your allowed sites — or it’s localhost and the key is live.Add it in Developers → Allowed sites; it works from the next view. Then make a new link.

Questions

Do my users need a Carousel account?

No. Your platform signs them in; Carousel trusts your server to ask only for reports they may see.

Can I try it before going live?

Yes: with a test key, the link opens Carousel’s sample report. Add http://localhost:<port> to your allowed sites to frame it from your machine.

Can I email the link, or make it ahead of time?

No. It works once and only inside your platform. Make one each time a user opens a report.

What if my user opens the report in a new tab?

The new tab shows “This report opens inside your platform”. The frame keeps working.

Can they download the report?

Yes. The PDF tab in the frame previews it and downloads it to their device. It doesn’t pass through your server.

An application has two applicants. How do I show both?

Each applicant is their own screening, so each has their own link and frame. Send each one with your record’s id as externalId, and ?externalId= finds them all. A co-applicant someone adds in Carousel has externalId null, so it isn’t found that way.

Each of my customers has their own domain. Do you need all of them?

Yes — each exact address the frame shows on, added in Developers → Allowed sites (up to 25). Add new ones as your customers arrive; they work from the next view. Wildcards aren’t accepted: past 25, show reports from pages on one domain of yours.

Applicant data

Show a report only to the people in your platform who need it to assess that application. Carousel records who you named for every view. See Carousel’s Privacy Policy and Terms. The Tenant Screening Acceptable Use Policy applies to your own systems and connected assistants too.

The infrastructureof yes.