/v1 replaces the unversioned private API at https://app.rallly.co/api/private. The private API is frozen: it still answers, but it receives no new fields or endpoints, and everything new lands on /v1 only.
Your API keys, poll IDs, invite links and rate limits carry over unchanged. What changes is the base URL, the request and response shapes, and how errors look.
Summary of changes
Global changes
Base URL
Replace the host and prefix. The poll paths under it are the same, with two exceptions:PATCH /polls/{pollId} is removed, and /docs has no v1 equivalent. Both are covered below.
Error envelope
Every v1 failure is JSON with the same shape, including cases the private API answered in plain text or with the validator’s raw output. Branch onerror.code; error.message is for humans.
UNAUTHORIZED, INVALID_AUTHORIZATION_HEADER, SPACE_NOT_PRO, ORGANIZER_NOT_MEMBER, TOO_MANY_OPTIONS, INAPPROPRIATE_CONTENT, POLL_NOT_FOUND, RATE_LIMIT_EXCEEDED and SERVICE_UNAVAILABLE keep their status codes and meanings. See Errors for the full list.
Strict request bodies
The private API silently dropped fields it did not know, includingspaceId, which it accepted but never used. v1 rejects any field outside the documented schema with VALIDATION_ERROR. Remove spaceId and any other extra fields before switching.
The poll resource
Every endpoint that returns a poll (POST /polls, GET /polls, GET /polls/{pollId}) returns the same v1 shape. Compared with the private API:
timezoneis renamedtimeZone.useris replaced byorganizer, which addsidandemail.kindis added:dateortime. It tells you which option shape to expect.updatedAtis added. Compare it between reads to notice changes.- The five poll settings are returned as booleans.
participantCountis on every poll, not only list items.- Options in a
datepoll carrydateinstead ofstartTimeandduration: 0. Options in atimepoll are unchanged.
Endpoints
Create a poll
POST /api/private/polls → POST /v1/polls
Status code. v1 responds 201 Created instead of 200 OK. A client that checks for exactly 200 will treat every successful create as a failure.
Request. The private API chose the poll type from whether you sent dates or slots. v1 takes an explicit kind and puts every option under options. Each request option is the response option without its id: { date } for a date poll, { startTime, duration? } for a time poll. The body is strict, so the other response fields (id, status, createdAt, participantCount and so on) must not be sent.
Date polls:
slots wrapper is gone. duration and timeZone move to the top level, explicit times become options objects, and slot generators move to their own generators array.
Everything else (
description, location, requireEmail, hideParticipants, hideScores, disableComments, allowTentativeVotes, organizer) is unchanged. The interpretation of a startTime without an offset is also unchanged: wall clock time in timeZone when set, floating otherwise.
Generator validation. The private API expanded a generator and only then failed with NO_OPTIONS_GENERATED if nothing came out. v1 rejects an impossible generator up front with VALIDATION_ERROR naming the field: to not later than from, a daily window shorter than duration, or a date range containing none of the listed days. Times in a generator are HH:mm; the private API also accepted seconds.
Response. The full poll, in the v1 shape.
List polls
GET /api/private/polls → GET /v1/polls
Request. Unchanged. status, cursor and limit work the same way, and nextCursor is still returned beside data.
Response. Each item is the full v1 poll. The private API’s list items already carried participantCount; the rest of the new fields are additions, and the renames (timezone, user, date options) apply here too.
Get a poll
GET /api/private/polls/{pollId} → GET /v1/polls/{pollId}
Request. Unchanged.
Response. The v1 poll. Note that participantCount is now included, so a client that called both GET /polls/{pollId} and GET /polls/{pollId}/results just to count participants can drop the second call.
Update a poll
PATCH /api/private/polls/{pollId} → removed
Polls close on their own once every option is in the past. To close a poll earlier, or to reopen one, use the app. If your integration polled results and stopped once the poll was closed, keep reading status from GET /polls/{pollId}/results; that part is unchanged.
Get poll results
GET /api/private/polls/{pollId}/results → GET /v1/polls/{pollId}/results
Request. Unchanged.
Response. Three changes:
kindis added at the top level.- Options in a
datepoll carrydateinstead ofstartTimeandduration: 0, matching the poll resource. votesis dense. The private API omitted any vote type with zero votes, so an option nobody voted on hadvotes: []. v1 lists every vote type the poll offers, in display order, withcount: 0where nobody chose it. A client that summedvotesor looked a type up bytypekeeps working; a client that treated an empty array as “no votes” must check the counts instead.
score, isTopChoice and highScore are computed the same way today, but v1 documents score as opaque: sort by it and use isTopChoice or highScore to find the leaders, but do not decode it into vote counts or compare it across polls. Vote types are an open set in v1, so tolerate a type you do not recognise.
Get poll participants
GET /api/private/polls/{pollId}/participants → GET /v1/polls/{pollId}/participants
Request. Unchanged.
Response. data is the array itself. The private API wrapped it in an object with pollId; v1 drops the wrapper, since you already know the poll ID from the URL. The participant fields (id, name, email, createdAt) and the ordering (oldest response first) are unchanged.
Delete a poll
DELETE /api/private/polls/{pollId} → DELETE /v1/polls/{pollId}
Unchanged. Responds 200 with { "data": { "id", "deleted": true } }, or 404 POLL_NOT_FOUND.
OpenAPI document and docs page
GET /api/private/openapi → GET /v1/openapi
The spec moves with the API. The Scalar page at /api/private/docs has no v1 equivalent; this site is the reference. Both spec endpoints are public and need no key.
Checklist
- Change the base URL to
https://api.rallly.co/v1. - Accept
201fromPOST /polls. - Rewrite create requests: add
kind, replacedatesandslotswithoptionsandgenerators, renametimezonetotimeZoneand the generator’sstartTimeandendTimetofromandto, and removespaceIdand any other undocumented fields. - Read
timeZoneinstead oftimezone, andorganizerinstead ofuser, in every poll response. - Branch on
kindwhen reading options:datefor date polls,startTimeanddurationfor time polls. - In results, stop treating an empty
votesarray as “no votes”; read the counts. - Read participants from
datadirectly. - Parse every error as
{ "error": { "code", "message" } }and drop any handling of the private API’s validator output,DUPLICATE_DATES,NO_OPTIONS_GENERATEDandTRANSITION_NOT_AVAILABLE. - Remove any call to
PATCH /polls/{pollId}.