> ## Documentation Index
> Fetch the complete documentation index at: https://docs.timeback.com/llms.txt
> Use this file to discover all available pages before exploring further.

# User Profile

> Fetch user profile data including XP, enrollments, and progress

## Overview

The user profile contains an enriched view of the current user, including identity information, school, grade, enrolled courses, goals, and XP totals.

You can access user data in several ways:

1. **Server-side**: verify users or fetch profiles directly from your backend
2. **Client-side**: use a framework hook with built-in state and caching

## Server-Side

The `timeback.user` namespace provides programmatic methods that accept an email directly. Use them in your own API routes, server actions, webhooks, cron jobs, or any backend context.

### Verify a user

Use `timeback.user.verify(email)` to check whether a Timeback user exists for a given email. This is a lightweight check — it does not fetch enrollments, analytics, or build an enriched profile.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const result = await timeback.user.verify('student@example.com')

  if (result.verified) {
  	console.log(result.timebackId) // "tb_abc123"
  }
  ```

  ```python Python theme={null}
  result = await timeback.user.verify("student@example.com")

  if result.verified:
      print(result.timeback_id)  # "tb_abc123"
  ```
</CodeGroup>

Useful for gating features (e.g., offering a free tier to Timeback users) without the cost of a full profile fetch.

<ResponseField name="verified" type="boolean">
  Whether the user exists in Timeback.
</ResponseField>

<ResponseField name="timebackId" type="string">
  The Timeback user ID. Only present when `verified` is `true`.
</ResponseField>

### Get a user profile

Use `timeback.user.getProfile(email)` to get the full enriched profile — identity, enrollments, courses, goals, and XP. This is the programmatic equivalent of the `/user/me` HTTP handler.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const email = 'student@example.com'
  const profile = await timeback.user.getProfile(email)
  ```

  ```python Python theme={null}
  email = "student@example.com"
  profile = await timeback.user.get_profile(email)
  ```
</CodeGroup>

Throws a `TimebackUserResolutionError` if the user cannot be resolved. If you're not sure whether a user exists, call `verify()` first.

<Expandable title="profile fields">
  <ResponseField name="id" type="string">
    Timeback user ID.
  </ResponseField>

  <ResponseField name="email" type="string">
    User's email address.
  </ResponseField>

  <ResponseField name="name" type="string">
    Display name.
  </ResponseField>

  <ResponseField name="school" type="object">
    School the user belongs to.

    <Expandable title="properties">
      <ResponseField name="id" type="string">
        School ID.
      </ResponseField>

      <ResponseField name="name" type="string">
        School name.
      </ResponseField>
    </Expandable>
  </ResponseField>

  <ResponseField name="grade" type="number">
    Grade level.
  </ResponseField>

  <ResponseField name="courses" type="object[]">
    Enrolled courses matched against `timeback.config.json`.

    <Expandable title="properties">
      <ResponseField name="id" type="string">
        Course ID.
      </ResponseField>

      <ResponseField name="code" type="string">
        Course code.
      </ResponseField>

      <ResponseField name="name" type="string">
        Course name.
      </ResponseField>
    </Expandable>
  </ResponseField>

  <ResponseField name="goals" type="object">
    Goals from the user's enrollment metadata.

    <Expandable title="properties">
      <ResponseField name="dailyXp" type="number">
        Daily XP target.
      </ResponseField>

      <ResponseField name="dailyLessons" type="number">
        Daily lesson target.
      </ResponseField>

      <ResponseField name="dailyActiveMinutes" type="number">
        Daily active minutes target.
      </ResponseField>

      <ResponseField name="dailyAccuracy" type="number">
        Daily accuracy target.
      </ResponseField>

      <ResponseField name="dailyMasteredUnits" type="number">
        Daily mastered units target.
      </ResponseField>
    </Expandable>
  </ResponseField>

  <ResponseField name="xp" type="object">
    XP earned on this app. Calculated from Caliper events sent during activity tracking.

    <Expandable title="properties">
      <ResponseField name="today" type="number">
        XP earned during the current UTC day.
      </ResponseField>

      <ResponseField name="all" type="number">
        Total XP over all time.
      </ResponseField>
    </Expandable>
  </ResponseField>
</Expandable>

## Client-Side

On the client, use the framework-specific profile hook. The hook handles loading state, caching, and session-aware refetching — you never call `timeback.user.fetch()` directly from the browser.

<Tabs>
  <Tab title="React">
    ```tsx theme={null}
    import { useTimebackProfile } from '@timeback/sdk/react'

    // Manual fetch
    function ProfileButton() {
    	const { state, canFetch, fetchProfile } = useTimebackProfile()

    	if (state.status === 'loaded') {
    		return <div>XP: {state.profile.xp.today}</div>
    	}

    	return (
    		<button onClick={fetchProfile} disabled={!canFetch}>
    			{state.status === 'loading' ? 'Loading...' : 'Load Profile'}
    		</button>
    	)
    }

    // Auto-fetch when verified
    function AutoProfile() {
    	const { state } = useTimebackProfile({ auto: true })

    	if (state.status === 'loading') return <div>Loading...</div>
    	if (state.status === 'loaded') return <div>XP: {state.profile.xp.today}</div>

    	return null
    }
    ```
  </Tab>

  <Tab title="Vue">
    ```vue theme={null}
    <script setup>
    import { useTimebackProfile } from '@timeback/sdk/vue'

    const { state, canFetch, fetchProfile } = useTimebackProfile()
    </script>

    <template>
      <div v-if="state.status === 'loaded'">
        XP: {{ state.profile.xp.today }}
      </div>
      <button v-else @click="fetchProfile" :disabled="!canFetch">
        {{ state.status === 'loading' ? 'Loading...' : 'Load Profile' }}
      </button>
    </template>
    ```
  </Tab>

  <Tab title="Svelte">
    ```svelte theme={null}
    <script>
      import {
        timebackProfile,
        timebackProfileCanFetch,
        fetchTimebackProfile
      } from '@timeback/sdk/svelte'
    </script>

    <button onclick={fetchTimebackProfile} disabled={!$timebackProfileCanFetch}>
      {$timebackProfile.status === 'loading' ? 'Loading...' : 'Load Profile'}
    </button>

    {#if $timebackProfile.status === 'loaded'}
      <div>XP: {$timebackProfile.profile.xp.today}</div>
    {/if}
    ```
  </Tab>

  <Tab title="Solid">
    ```tsx theme={null}
    import { createTimebackProfile } from '@timeback/sdk/solid'
    import { Show } from 'solid-js'

    function ProfileButton() {
    	const { state, canFetch, fetchProfile } = createTimebackProfile()

    	return (
    		<Show
    			when={state.status === 'loaded'}
    			fallback={
    				<button onClick={fetchProfile} disabled={!canFetch}>
    					{state.status === 'loading' ? 'Loading...' : 'Load Profile'}
    				</button>
    			}
    		>
    			<div>XP: {state.profile.xp.today}</div>
    		</Show>
    	)
    }
    ```
  </Tab>
</Tabs>

<Expandable title="profile fields">
  <ResponseField name="id" type="string">
    Timeback user ID.
  </ResponseField>

  <ResponseField name="email" type="string">
    User's email address.
  </ResponseField>

  <ResponseField name="name" type="string">
    Display name.
  </ResponseField>

  <ResponseField name="school" type="object">
    School the user belongs to.

    <Expandable title="properties">
      <ResponseField name="id" type="string">
        School ID.
      </ResponseField>

      <ResponseField name="name" type="string">
        School name.
      </ResponseField>
    </Expandable>
  </ResponseField>

  <ResponseField name="grade" type="number">
    Grade level.
  </ResponseField>

  <ResponseField name="courses" type="object[]">
    Enrolled courses matched against `timeback.config.json`.

    <Expandable title="properties">
      <ResponseField name="id" type="string">
        Course ID.
      </ResponseField>

      <ResponseField name="code" type="string">
        Course code.
      </ResponseField>

      <ResponseField name="name" type="string">
        Course name.
      </ResponseField>
    </Expandable>
  </ResponseField>

  <ResponseField name="goals" type="object">
    Goals from the user's enrollment metadata.

    <Expandable title="properties">
      <ResponseField name="dailyXp" type="number">
        Daily XP target.
      </ResponseField>

      <ResponseField name="dailyLessons" type="number">
        Daily lesson target.
      </ResponseField>

      <ResponseField name="dailyActiveMinutes" type="number">
        Daily active minutes target.
      </ResponseField>

      <ResponseField name="dailyAccuracy" type="number">
        Daily accuracy target.
      </ResponseField>

      <ResponseField name="dailyMasteredUnits" type="number">
        Daily mastered units target.
      </ResponseField>
    </Expandable>
  </ResponseField>

  <ResponseField name="xp" type="object">
    XP earned on this app. Calculated from Caliper events sent during activity tracking.

    <Expandable title="properties">
      <ResponseField name="today" type="number">
        XP earned during the current UTC day.
      </ResponseField>

      <ResponseField name="all" type="number">
        Total XP over all time.
      </ResponseField>
    </Expandable>
  </ResponseField>
</Expandable>

### Hook State

The profile hook returns a state object with these statuses:

| Status    | Description                       |
| --------- | --------------------------------- |
| `idle`    | Initial state, no fetch attempted |
| `loading` | Fetch in progress                 |
| `loaded`  | Profile successfully loaded       |
| `error`   | Fetch failed                      |

### Caching

The client SDK caches profile data to minimize API calls:

* Profile is fetched once per session
* Manual refetch available via `fetchProfile()`
* Cache is invalidated on sign-out

## Next Steps

<CardGroup cols={2}>
  <Card title="Custom Activities" icon="chart-line" href="/beta/build-on-timeback/sdk/activity-tracking/intro">
    Track activities to earn XP
  </Card>

  <Card title="Identity" icon="user" href="/beta/build-on-timeback/sdk/identity">
    Authentication setup
  </Card>

  <Card title="EduBridge Client" icon="plug" href="/beta/build-on-timeback/clients/edubridge">
    Advanced analytics queries
  </Card>

  <Card title="OneRoster Client" icon="plug" href="/beta/build-on-timeback/clients/oneroster">
    Query enrollments directly
  </Card>
</CardGroup>
