Rising Tide Developers
SDKCore

rtClient.waitlist

SDK methods for taking requests to be let in, reading one back, and the pool members spend invites on.

The public half of the waitlist. signup, status and openSpots work with only an app API key - no signed-in user, which is the point: the person asking has no account, and creating one for them before they are let in is the dead end this surface exists to remove.

A signup is a request, not an invite. Nothing here mints, funds, or grants anything. An operator converts a request into an invite by hand, through the ordinary mint-and-send path.

Two more reads serve other callers: openSpots is public, and pool is for signed-in members. The operator queue - list, approve, decline, publish, withdraw, annotate, requeue and remove - is admin-only and is not something a client app calls. See the Waitlist API reference.

Methods

rtClient.waitlist.openSpots()

How many applicants are let in on the spot right now. Public, like signup. Returns ApiResponse<WaitlistOpenSpots>; open is null when nobody is. While spots are open, signup returns a request that is already INVITED, so its status carries a claimUrl at once. Say it as "right now", never as a promise - a slot can go before someone submits.


rtClient.waitlist.pool(params?)

The applicants who agreed to be read, for the people who can act on it - the pool a holder spends an invite on with invites.sendToWaitlist. Needs a signed-in member (401 for anyone else). The order is shuffled per viewer and stable across their pages; it is not a ranking and there is no total.


rtClient.waitlist.signup(input)

Record a request. Returns ApiResponse<WaitlistSignupResult> - { statusToken }, and nothing else.

const { data } = await rtClient.waitlist.signup({
  channel: "EMAIL",              // or "X_HANDLE"
  contact: "ana@gmail.com",      // as typed; normalized server-side
  pitch: "...",                  // optional - at most 800 characters after trimming
  currentWork: "...",            // optional - at most 500 characters after trimming
  interests: ["WRITING"],        // at least one
  experience: "NEW_TO_CRYPTO",
  referrerAppId: 10,             // optional - where they came from; never ask for it
});

console.log(data.statusToken);

A duplicate returns the same 200, the same body and the same token as a new signup. Do not branch on new-versus-duplicate - you cannot see it, deliberately, because a different response would let anyone test whether a given person had applied. A contact that does not parse still returns a normal 400 with INVALID_CONTACT: that is the submitter's own typo, not information about a third party.

The contact is normalized with the same rules the invite bindings use, so Ana+RT@Gmail.com and ana@gmail.com are one row, as are @Handle, handle and HANDLE.

Rate-limited per IP and fail-closed, so treat RATE_LIMITED as a real state your form must render.


rtClient.waitlist.status(token)

Read one request back by its statusToken. Returns ApiResponse<WaitlistStatus>.

const { data } = await rtClient.waitlist.status(token);

if (data.status === "INVITED") {
  // data.claimUrl, data.contactRaw, data.expiresAt
}

Nothing is ever sent to an applicant - there is no mail channel and no way to DM an X handle - so the token is the whole thread back to the request. Store it and hand it to the person as a URL; a browser-local copy is a useful second chance, never a replacement for the link.

While PENDING the payload is just status and createdAt. Once INVITED it also carries claimUrl, expiresAt, invitedAt and contactRaw.

contactRaw is the address they typed, and this is the only response allowed to name it. InvitePreview.boundTo carries the binding TYPE only - no recipient-enumeration oracle - so a claim page can say "sign in with Google" but never which Google account, which is a loop for anyone with two of them. This payload is scoped to the person who filled in the form, so echoing their own submission back leaks nothing and removes the cliff.

An unrecognized token resolves to { status: "UNKNOWN" } with a 200, never a 404. The endpoint never confirms whether a token was real, so render UNKNOWN the same way you render a request that has not been accepted.