Skip to documentation

Documentation

Platform API

tRPC API reference

The frozen tRPC procedure inventory, access boundaries, current handler state, and shared schema shapes.

Transport and authentication

The API uses tRPC 11 over HTTP. Queries use GET and encode non-empty input in the input query parameter; mutations use POST. Prefer a tRPC client so batching and response envelopes remain correct.

Raw public query
const response = await fetch('https://api.facoolta.com/trpc/health.check', {
  headers: { accept: 'application/json' },
});

if (!response.ok) throw new Error(`API returned ${response.status}`);
const envelope = await response.json();

Protected requests send Authorization: Bearer <access-token>. Verified procedures additionally require current campus verification. Public procedures ignore the absence of a session but still validate input.

Procedure status

The checked-in contract freezes 40 procedure paths. The live label means the server contains a handler; it is not an availability or uptime claim. A stub path is part of the contract but currently throws INTERNAL_SERVER_ERROR with the message NOT_IMPLEMENTED.

Health and identity

Health, institution, and identity procedures
Procedure Operation Access Input Output State
health.check query public void Health live
auth.me query bearer void Me live
institutions.list query public void Institution[] live
institutions.get query public { id: UUID } Institution live
verification.status query bearer void VerificationStatus live
verification.submit mutation bearer VerificationSubmit stub

Communities and realtime

Community, discussion, and realtime-token procedures
Procedure Operation Access Input Output State
communities.list query verified void Community[] live
communities.get query verified { id: UUID } Community live
communities.members query verified { id: UUID } CommunityMember[] live
discussions.list query verified void Discussion[] live
discussions.get query verified { id: UUID } Discussion live
talk.openDirect mutation verified { userId: UUID } Discussion live
talk.sync mutation verified { discussionId: UUID } { streamChannelId; memberCount } live
tokens.stream query verified void StreamToken live
tokens.livekit mutation verified { discussionId: UUID } LiveKitToken live

Resource lookups enforce membership and return NOT_FOUND when the object is absent or inaccessible. The LiveKit token mutation checks discussion access before issuing a token. talk.openDirect rejects self-messaging, unverified users, and blocked pairs; talk.sync reconciles authorized discussion members with the Stream channel.

unidrive

unidrive procedures
Procedure Operation Access Input Output State
unidrive.listShared query verified void DriveItem[] live
unidrive.get query verified { id: UUID } DriveItem live
unidrive.createShare mutation verified CreateShare stub
unidrive.grant mutation verified GrantAccess stub
unidrive.revoke mutation verified RevokeAccess stub
unidrive.signedUrl query verified { id: UUID } SignedUrl live

Read operations enforce item access. unidrive.signedUrl requires read access and a stored object path; its URL is temporary and includes an ISO expiry timestamp.

studate

studate procedures
Procedure Operation Access Input Output State
studate.profile query verified void studateProfile | null live
studate.upsertProfile mutation verified { age; bio; visible } stub
studate.discover query verified void studateProfile[] live
studate.like mutation verified { userId: UUID } stub
studate.pass mutation verified { userId: UUID } stub
studate.lock query verified void studateLock | null live
studate.continueLock mutation verified void studateLock live
studate.endLock mutation verified void void live
studate.setAvailability mutation verified { blocks: Availability[] } void live
studate.block mutation verified { userId: UUID } stub
studate.report mutation verified { userId; reason } stub

All studate procedures require a verified session. Profiles enforce age 18–99, bios are capped at 500 characters, and availability accepts at most 40 blocks using HH:MM times. The current discovery handler returns an empty list.

ginny and operations

ginny, push, report, and backup procedures
Procedure Operation Access Input Output State
ginny.complete mutation public { installationId; prompt } { text: string } live
ginny.enqueue mutation bearer ginnyEnqueue ginnyJob live
ginny.listJobs query bearer void ginnyJob[] live
ginny.getJob query bearer { id: UUID } ginnyJob live
push.register mutation bearer PushRegistration void live
push.unregister mutation bearer PushRegistration void live
reports.bug mutation bearer { message; route? } void live
backups.list query bearer void BackupMeta[] live

Push tokens are restricted to ios or android and 8–4096 characters. Bug messages are 1–4000 characters; an optional route is capped at 200 characters. ginny jobs accept the kinds summarize, study_pack, or research; the optional opaque contextRef is capped at 2,000 characters.

Shared schemas

UUID fields use canonical UUID strings. Date-time fields use ISO 8601 date-time strings. The excerpts below mirror the shared contract; import the schemas from @facoolta/contract instead of duplicating them in workspace code.

Core shapes
type UUID = string;
type IsoDateTime = string;

type Institution = { id: UUID; slug: string; name: string };

type Me = {
  userId: UUID;
  email: string | null;
  institutionId: UUID | null;
  verification: 'none' | 'pending' | 'verified' | 'expired' | 'rejected';
};

type Community = {
  id: UUID;
  institutionId: UUID;
  kind: 'course' | 'campus' | 'group';
  slug: string;
  title: string;
};

type Discussion = {
  id: UUID;
  communityId: UUID | null;
  kind: 'course' | 'group' | 'direct' | 'studate';
  streamChannelId: string;
  createdAt: IsoDateTime;
};
Domain shapes
type DriveItem = {
  id: UUID; ownerId: UUID; parentId: UUID | null;
  kind: 'file' | 'folder'; title: string; createdAt: IsoDateTime;
};

type studateProfile = { userId: UUID; age: number; bio: string; visible: boolean };
type studateLock = {
  id: UUID; matchId: UUID; otherUserId: UUID;
  phase: '3d' | '7d' | '30d' | '90d' | 'ongoing';
  status: 'active' | 'decision' | 'ended';
  lockedAt: IsoDateTime; lockEndsAt: IsoDateTime | null;
};

type Availability = { dayOfWeek: 0 | 1 | 2 | 3 | 4 | 5 | 6; startTime: string; endTime: string };
type ginnyJobKind = 'summarize' | 'study_pack' | 'research';
type ginnyEnqueue = {
  title: string; // 1..200 characters
  kind: ginnyJobKind;
  contextRef?: string; // opaque reference, at most 2,000 characters
};
type ginnyJob = {
  id: UUID; title: string; kind: ginnyJobKind;
  status: 'queued' | 'running' | 'done' | 'needs_you';
  triggerRunId: string | null; resultSummary: string | null;
  createdAt: IsoDateTime;
};
CreateShare
{ title: string(1..200); kind: file | folder; parentId?: UUID | null }
GrantAccess
{ itemId: UUID; access: read | write; exactly one of userId or communityId }
RevokeAccess
{ itemId: UUID; exactly one of userId or communityId }
VerificationSubmit
{ institutionId: UUID; method: lms_escrow | device_attested }

Errors

CodeMeaningClient action
UNAUTHORIZEDSession is missing or invalid.Refresh authentication once, then require sign-in.
NOT_FOUNDObject is absent or the caller lacks access.Do not reveal which case occurred.
PRECONDITION_FAILEDThe requested state transition is not allowed.Refresh current state before retrying.
TOO_MANY_REQUESTSThe ginny installation limit was reached.Back off; do not rotate installation IDs.
INTERNAL_SERVER_ERRORUnexpected failure or an explicit stub.Check for NOT_IMPLEMENTED; otherwise record request context.