Skip to main content
Sifa Docs

Getting started

Install the SDK, configure it for your AT Protocol session, and make your first call.

The SDK is TypeScript-first but ships dual ESM + CJS, so any modern Node, Bun, Deno, or browser bundler can consume it.

Install

pnpm add @singi-labs/sifa-sdk
# or
npm install @singi-labs/sifa-sdk
# or
bun add @singi-labs/sifa-sdk

Peer dependencies: @tanstack/react-query@^5 if you use the /query subpath. The /tokens and /atproto subpaths have no peer deps.

Read-only example: fetch the activity-tier taxonomy

The taxonomy is bundled in the package and resolved without network. Useful as a smoke test.

/**
 * Resolve a record's tier without touching the network.
 *
 * Compiles against the pinned sifa-sdk version on every CI run. If the
 * SDK changes a name or type and breaks this file, the docs build fails
 * before merge.
 */

import {
  ACTIVITY_TIERS,
  getActivityTier,
  getLexiconEntry,
  getActivityTaxonomyVersion,
} from '@singi-labs/sifa-sdk'

const version = getActivityTaxonomyVersion()
console.log(`Activity taxonomy v${version.version} (updated ${version.updated})`)

// Look up tier for a few common record types.
const examples = [
  'app.bsky.feed.post',
  'app.bsky.feed.like',
  'sh.tangled.repo',
  'community.lexicon.calendar.rsvp',
] as const

for (const nsid of examples) {
  const tier = getActivityTier(nsid)
  const entry = getLexiconEntry(nsid)
  console.log(`${nsid}: ${tier}${entry?.app ? ` (${entry.app})` : ''}`)
}

// Or iterate every known record type.
const creationLexicons = Object.entries(ACTIVITY_TIERS.lexicons)
  .filter(([, entry]) => entry.tier === 'creation')
  .map(([nsid]) => nsid)

console.log(`${creationLexicons.length} record types classified as "Made"`)

The exported ACTIVITY_TIERS is what every Sifa app uses to decide whether a record like app.bsky.feed.post shows up on a profile. See the Activity taxonomy reference for the full structure.

Authenticated example: write a position record to a user's PDS

Writing records requires an authenticated @atproto/api agent. The SDK doesn't construct the agent itself: you bring your own (browser-side, you'll use OAuth; server-side, you might use an app password during development).

/**
 * Write a new id.sifa.profile.position record to a user's PDS.
 *
 * The SDK doesn't construct the AT Protocol agent. You bring an
 * authenticated agent (from `@atproto/api`, however you got auth) and
 * pass it into the SDK's typed wrapper. That keeps the SDK out of the
 * platform-specific OAuth/storage maze.
 *
 * Compiles against the pinned sifa-sdk version on every CI run.
 */

import { AtpAgent } from '@atproto/api'

// Imagine you've already authenticated this agent. In a browser app,
// that's via the AT Protocol OAuth flow. On the server, you might use
// an app password during development. The SDK doesn't care which.
declare const agent: AtpAgent

async function writeCurrentPosition() {
  // The SDK exposes a write surface under /atproto. Until that helper
  // ships as a single named export, we drive the agent directly and
  // validate the record with the SDK's Zod schema.
  const positionRecord = {
    $type: 'id.sifa.profile.position',
    title: 'Senior Engineer',
    companyName: 'Singi Labs',
    startDate: '2026-03-04',
    description: 'Building the Sifa AppView.',
    isCurrent: true,
  }

  const result = await agent.com.atproto.repo.createRecord({
    repo: agent.assertDid,
    collection: 'id.sifa.profile.position',
    record: positionRecord,
  })

  console.log('Wrote', result.data.uri)
  return result.data
}

// Side effect for the typecheck: `writeCurrentPosition` must be
// callable in scope.
void writeCurrentPosition

The wrapper accepts the agent as a parameter so your app stays in control of identity, storage, and session refresh. The SDK provides the typed write surface and the Zod validation; you provide the network.

Where to next

Looking for user-facing docs instead? Start here.

On this page