@masheev/client
v0.1.0
Published
Masheev API client — tRPC + Better Auth for React, TanStack, and server
Readme
@masheev/client
Type-safe API client for Masheev. Wraps tRPC and Better Auth with sub-path exports for React, server-side, and TanStack Start environments.
Installation
pnpm add @masheev/clientEntry Points
| Import path | Use case |
|---|---|
| @masheev/client | Vanilla TS/JS — direct query() / mutate() calls |
| @masheev/client/react | React — TanStack Query hooks (useQuery, useMutation) |
| @masheev/client/server | Server-side — API token / API key auth |
| @masheev/client/tanstack | TanStack Start — automatic cookie forwarding |
| @masheev/client/types | Type-only — Organization, User, Session, etc. |
Quick Start
React
import { apiClient, authClient } from "@masheev/client/react";
function InboxList() {
const { data: session } = authClient.useSession();
const { data, isLoading } = apiClient.inboxes.list.useQuery(
{ orgId: session?.user.activeOrganizationId! },
{ enabled: !!session },
);
if (isLoading) return <p>Loading...</p>;
return (
<ul>
{data?.inboxes.map((inbox) => (
<li key={inbox.id}>{inbox.name}</li>
))}
</ul>
);
}Vanilla
import { apiClient, authClient } from "@masheev/client";
const session = await authClient.getSession();
const { inboxes } = await apiClient.inboxes.list.query({
orgId: "org_123",
});Server-side
import { serverApi, createServerApiClients } from "@masheev/client/server";
// Pre-configured (reads TEMPLATE_API_TOKEN / TEMPLATE_API_KEY env vars)
const { inboxes } = await serverApi.inboxes.list.query({ orgId: "org_123" });
// Or create a custom client
const { apiClient } = createServerApiClients({
baseURL: "https://api.masheev.com",
apiToken: process.env.MY_API_TOKEN,
});Authentication (authClient)
Session
authClient.getSession() // Promise — current session
authClient.useSession() // React hook — { data, isPending }Sign In / Sign Up
authClient.signIn.email({ email, password })
authClient.signUp.email({ email, password, name? })
authClient.magicLink.sendEmail({ email, callbackURL? })
authClient.signIn.oauth(provider, options?)
authClient.signOut()Password Management
authClient.changePassword({ currentPassword, newPassword })
authClient.resetPassword({ email, redirectURL })
authClient.updatePassword({ token, newPassword })Two-Factor Authentication
authClient.twoFactor.enable({ password }) // returns secret + backup codes
authClient.twoFactor.disable({ password })
authClient.twoFactor.getTotpUri()
authClient.twoFactor.generateBackupCodes({ password })
authClient.twoFactor.verifyTotp({ code })Organization Management
authClient.organization.create({ name, slug? })
authClient.organization.list()
authClient.organization.setActive({ organizationId })
authClient.organization.update({ organizationId, name?, slug? })
authClient.organization.delete({ organizationId })
authClient.organization.getFullOrganization({ organizationId })
// React hooks
authClient.useListOrganizations()
authClient.useActiveOrganization()
authClient.useActiveMember()
authClient.useActiveMemberRole()Members & Invitations
authClient.organization.inviteMember({ organizationId, email, role? })
authClient.organization.acceptInvitation({ invitationId })
authClient.organization.cancelInvitation({ invitationId })Teams
authClient.organization.createTeam({ organizationId, name })
authClient.organization.listTeams({ organizationId })
authClient.organization.updateTeam({ organizationId, teamId, name? })
authClient.organization.deleteTeam({ organizationId, teamId })
authClient.organization.addTeamMember({ organizationId, teamId, memberId, role? })
authClient.organization.removeTeamMember({ organizationId, teamId, memberId })API Keys
authClient.apiKey.create({ name? })
authClient.apiKey.list()
authClient.apiKey.get({ keyId })
authClient.apiKey.delete({ keyId })User Profile
authClient.updateUser({ name?, image? })
authClient.deleteUser()
authClient.changeEmail({ newEmail, password })
authClient.sendVerificationEmail({ email })tRPC Routers (apiClient)
Every router supports type-safe inputs and outputs. In React, replace .query() with .useQuery() and .mutate() with .useMutation().
Inboxes
apiClient.inboxes.list.query({ orgId, channel? })
apiClient.inboxes.get.query({ id })
apiClient.inboxes.create.mutate({ orgId, name, channel, ... })
apiClient.inboxes.update.mutate({ id, name?, ... })
apiClient.inboxes.delete.mutate({ id })Contacts
apiClient.contacts.list.query({ orgId, search?, limit?, cursor? })
apiClient.contacts.get.query({ id })
apiClient.contacts.find.query({ orgId, email?, phone?, externalId? })
apiClient.contacts.create.mutate({ orgId, name?, email?, phone?, ... })
apiClient.contacts.update.mutate({ id, name?, ... })
apiClient.contacts.delete.mutate({ id })
apiClient.contacts.merge.mutate({ orgId, winnerId, loserId })
apiClient.contacts.export.query({ id }) // GDPR data export
apiClient.contacts.anonymize.mutate({ id }) // GDPR erasureConversations
apiClient.conversations.list.query({ orgId, status?, inboxId?, assigneeId?, ... })
apiClient.conversations.get.query({ id })
apiClient.conversations.create.mutate({ orgId, inboxId, contactId, ... })
apiClient.conversations.update.mutate({ id, version, ... }) // optimistic locking
apiClient.conversations.assign.mutate({ id, assigneeId? })
apiClient.conversations.resolve.mutate({ id, outcome? })
apiClient.conversations.snooze.mutate({ id, until })
apiClient.conversations.delete.mutate({ id })
apiClient.conversations.batchUpdate.mutate({ ids, action, ... })Messages
apiClient.messages.list.query({ conversationId, cursor?, limit? })
apiClient.messages.get.query({ id })
apiClient.messages.create.mutate({ conversationId, content, channel?, ... })
apiClient.messages.updateStatus.mutate({ id, status })
apiClient.messages.markRead.mutate({ conversationId })
apiClient.messages.delete.mutate({ id })Webhooks
apiClient.webhooks.eventTypes.query({})
apiClient.webhooks.list.query({ orgId })
apiClient.webhooks.get.query({ id, orgId })
apiClient.webhooks.create.mutate({ orgId, url, events, name?, ... })
apiClient.webhooks.update.mutate({ id, orgId, status?, ... })
apiClient.webhooks.delete.mutate({ id, orgId })
apiClient.webhooks.test.mutate({ id, orgId })
apiClient.webhooks.regenerateSecret.mutate({ id, orgId })AI Agents
apiClient.aiAgents.list.query({ orgId, status? })
apiClient.aiAgents.get.query({ orgId, agentId })
apiClient.aiAgents.create.mutate({ orgId, name, systemPrompt, ... })
apiClient.aiAgents.update.mutate({ orgId, agentId, ... })
apiClient.aiAgents.delete.mutate({ orgId, agentId })Voice Agents
apiClient.voiceAgents.list.query({ orgId })
apiClient.voiceAgents.get.query({ orgId, agentId })
apiClient.voiceAgents.create.mutate({ orgId, ... })
apiClient.voiceAgents.update.mutate({ orgId, agentId, ... })
apiClient.voiceAgents.sync.mutate({ orgId, agentId })
apiClient.voiceAgents.delete.mutate({ orgId, agentId })Knowledge
apiClient.knowledge.list.query({ orgId })
apiClient.knowledge.get.query({ id })
apiClient.knowledge.create.mutate({ orgId, ... })
apiClient.knowledge.update.mutate({ id, ... })
apiClient.knowledge.delete.mutate({ id })
apiClient.knowledge.stats.query({ orgId })Labels
apiClient.labels.list.query({ orgId })
apiClient.labels.get.query({ id })
apiClient.labels.create.mutate({ orgId, name, color? })
apiClient.labels.update.mutate({ id, name?, color? })
apiClient.labels.delete.mutate({ id })Notes
apiClient.notes.list.query({ conversationId })
apiClient.notes.get.query({ id })
apiClient.notes.create.mutate({ conversationId, content })
apiClient.notes.update.mutate({ id, content })
apiClient.notes.delete.mutate({ id })Escalations
apiClient.escalations.list.query({ orgId, ... })
apiClient.escalations.get.query({ id })
apiClient.escalations.create.mutate({ orgId, conversationId, reason?, ... })
apiClient.escalations.assign.mutate({ id, userId })
apiClient.escalations.resolve.mutate({ id })
apiClient.escalations.dismiss.mutate({ id })
apiClient.escalations.stats.query({ orgId })Canned Responses
apiClient.cannedResponses.list.query({ orgId })
apiClient.cannedResponses.get.query({ id })
apiClient.cannedResponses.create.mutate({ orgId, title, content })
apiClient.cannedResponses.update.mutate({ id, title?, content? })
apiClient.cannedResponses.delete.mutate({ id })Phone Numbers
apiClient.phoneNumbers.list.query({ orgId })
apiClient.phoneNumbers.get.query({ id })
apiClient.phoneNumbers.create.mutate({ orgId, ... })
apiClient.phoneNumbers.update.mutate({ id, ... })
apiClient.phoneNumbers.delete.mutate({ id })Other Routers
apiClient.billing.getBalance.query({ orgId })
apiClient.billing.getPlan.query({ orgId })
apiClient.auditLogs.list.query({ orgId, ... })
apiClient.voices.list.query({ orgId })
apiClient.org.panelStats.query({ orgId })
apiClient.files.* // file upload/download
apiClient.chat.* // chat operations
apiClient.realtime.* // real-time subscriptions
apiClient.admin.organizations.list.query(...) // system admin
apiClient.admin.stats.getOverview.query({}) // system adminTypes
import type {
Organization,
Member,
Team,
Invitation,
User,
Session,
ApiKey,
OrganizationWithParsedMeta,
TeamWithParsedMeta,
} from "@masheev/client/types";Error Handling
tRPC errors are thrown as TRPCClientError with standard codes:
| Code | Meaning |
|---|---|
| UNAUTHORIZED | Missing or invalid session |
| FORBIDDEN | Insufficient permissions |
| NOT_FOUND | Resource does not exist |
| BAD_REQUEST | Invalid input |
| CONFLICT | Optimistic locking version mismatch |
import { TRPCClientError } from "@trpc/client";
try {
await apiClient.inboxes.get.query({ id: "bad" });
} catch (err) {
if (err instanceof TRPCClientError) {
console.error(err.message, err.data?.code);
}
}Configuration
The client reads its base URL from @masheev/shared/config:
| Environment | API URL | tRPC URL |
|---|---|---|
| Development | http://localhost:3015 | http://localhost:3015/api |
| Production | https://api.masheev.com | https://api.masheev.com/api |
Server clients accept an explicit baseURL override via createServerApiClients().
