> ## 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.

# CASE

> CASE API client for competency frameworks and academic standards

## Overview

The `@timeback/case` package provides a client for the CASE (Competency and Academic Standards Exchange) API, enabling:

* **Documents**: List and retrieve curriculum framework documents
* **Items**: List and retrieve individual competencies and learning objectives
* **Associations**: Retrieve relationships between CASE entities
* **Packages**: Upload, update, and retrieve complete framework bundles

## Installation

<CodeGroup>
  ```bash npm theme={null}
  npm install @timeback/case
  ```

  ```bash pnpm theme={null}
  pnpm add @timeback/case
  ```

  ```bash yarn theme={null}
  yarn add @timeback/case
  ```

  ```bash bun theme={null}
  bun add @timeback/case
  ```

  ```bash pip theme={null}
  pip install timeback-case
  ```

  ```bash uv theme={null}
  uv add timeback-case
  ```
</CodeGroup>

## Quick Start

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { CaseClient } from '@timeback/case'

  const client = new CaseClient({
  	env: 'staging',
  	auth: {
  		clientId: process.env.CASE_CLIENT_ID!,
  		clientSecret: process.env.CASE_CLIENT_SECRET!,
  	},
  })

  // List all framework documents
  const { CFDocuments } = await client.documents.list()

  // Get a specific item
  const { CFItem } = await client.items.get('item-sourced-id')

  // Upload a framework package
  const result = await client.packages.create(packageInput)
  ```

  ```python Python theme={null}
  from timeback_case import CaseClient

  client = CaseClient(
      env="staging",
      client_id=os.environ["CASE_CLIENT_ID"],
      client_secret=os.environ["CASE_CLIENT_SECRET"],
  )

  # List all framework documents
  documents = await client.documents.list()

  # Get a specific item
  item = await client.items.get("item-sourced-id")

  # Upload a framework package
  result = await client.packages.create(package_input)
  ```
</CodeGroup>

## Documents

Curriculum framework documents — the top-level containers for standards.

<CodeGroup>
  ```typescript TypeScript theme={null}
  client.documents.list()
  client.documents.list({ limit: 50, offset: 0 })
  client.documents.get(sourcedId)
  ```

  ```python Python theme={null}
  await client.documents.list()
  await client.documents.list({"limit": 50, "offset": 0})
  await client.documents.get(sourced_id)
  ```
</CodeGroup>

| Method   | Returns                         | Description                  |
| -------- | ------------------------------- | ---------------------------- |
| `list()` | `{ CFDocuments: CFDocument[] }` | List all framework documents |
| `get()`  | `{ CFDocument: CFDocument }`    | Get a document by sourcedId  |

## Items

Individual competencies, standards, and learning objectives within a framework.

<CodeGroup>
  ```typescript TypeScript theme={null}
  client.items.list()
  client.items.list({ limit: 100, offset: 0 })
  client.items.get(sourcedId)
  ```

  ```python Python theme={null}
  await client.items.list()
  await client.items.list({"limit": 100, "offset": 0})
  await client.items.get(sourced_id)
  ```
</CodeGroup>

| Method   | Returns                 | Description              |
| -------- | ----------------------- | ------------------------ |
| `list()` | `{ CFItems: CFItem[] }` | List all framework items |
| `get()`  | `{ CFItem: CFItem }`    | Get an item by sourcedId |

## Associations

Relationships between CASE entities (e.g., "is child of", "is related to").

<CodeGroup>
  ```typescript TypeScript theme={null}
  client.associations.get(sourcedId)
  ```

  ```python Python theme={null}
  await client.associations.get(sourced_id)
  ```
</CodeGroup>

| Method  | Returns                            | Description                     |
| ------- | ---------------------------------- | ------------------------------- |
| `get()` | `{ CFAssociation: CFAssociation }` | Get an association by sourcedId |

## Packages

Complete framework bundles containing documents, items, and associations. Supports Zod schema validation on inputs.

### Upload a Package

<CodeGroup>
  ```typescript TypeScript theme={null}
  const result = await client.packages.create({
  	CFDocument: {
  		identifier: 'doc-id',
  		uri: 'https://example.edu/frameworks/math-k12',
  		title: 'K-12 Math Standards',
  		creator: 'Example District',
  		// ... additional document fields
  	},
  	CFItems: [
  		{
  			identifier: 'item-1',
  			uri: 'https://example.edu/frameworks/math-k12/items/1',
  			fullStatement: 'Understand addition within 20',
  			// ... additional item fields
  		},
  	],
  	CFAssociations: [
  		// ... relationships between items
  	],
  })
  ```

  ```python Python theme={null}
  result = await client.packages.create({
      "CFDocument": {
          "identifier": "doc-id",
          "uri": "https://example.edu/frameworks/math-k12",
          "title": "K-12 Math Standards",
          "creator": "Example District",
          # ... additional document fields
      },
      "CFItems": [
          {
              "identifier": "item-1",
              "uri": "https://example.edu/frameworks/math-k12/items/1",
              "fullStatement": "Understand addition within 20",
              # ... additional item fields
          },
      ],
      "CFAssociations": [
          # ... relationships between items
      ],
  })
  ```
</CodeGroup>

### All Package Methods

<CodeGroup>
  ```typescript TypeScript theme={null}
  client.packages.create(packageInput)
  client.packages.update(sourcedId, packageInput)
  client.packages.upsert(sourcedId, packageInput)
  client.packages.get(sourcedId)
  client.packages.getGroups(sourcedId)
  ```

  ```python Python theme={null}
  await client.packages.create(package_input)
  await client.packages.update(sourced_id, package_input)
  await client.packages.upsert(sourced_id, package_input)
  await client.packages.get(sourced_id)
  await client.packages.get_groups(sourced_id)
  ```
</CodeGroup>

| Method        | Returns                                        | Description                            |
| ------------- | ---------------------------------------------- | -------------------------------------- |
| `create()`    | `CFPackageUploadResult`                        | Upload a complete framework package    |
| `update()`    | `CFPackageUploadResult`                        | Replace a package by sourcedId         |
| `upsert()`    | `CFPackageUploadResult`                        | Create or replace a package            |
| `get()`       | `{ CFPackage: CFPackage }`                     | Get a package by sourcedId             |
| `getGroups()` | `{ CFPackageWithGroups: CFPackageWithGroups }` | Get a package with hierarchical groups |

## Standalone vs Composed

The client works standalone or composed into `@timeback/core`:

<CodeGroup>
  ```typescript TypeScript theme={null}
  // Standalone
  import { CaseClient } from '@timeback/case'
  const client = new CaseClient({ env: 'staging', auth })

  // Composed
  import { TimebackClient } from '@timeback/core'
  const timeback = new TimebackClient({ env: 'staging', auth })
  timeback.case.documents.list()
  ```

  ```python Python theme={null}
  # Standalone
  from timeback_case import CaseClient
  client = CaseClient(env="staging", client_id=client_id, client_secret=client_secret)

  # Composed (note: case_ avoids Python keyword conflict)
  from timeback_core import TimebackClient
  timeback = TimebackClient(env="staging", client_id=client_id, client_secret=client_secret)
  await timeback.case_.documents.list()
  ```
</CodeGroup>

## Error Handling

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { CaseError, NotFoundError } from '@timeback/case/errors'

  try {
  	await client.documents.get('invalid-id')
  } catch (error) {
  	if (error instanceof NotFoundError) {
  		console.log('Document not found')
  	} else if (error instanceof CaseError) {
  		console.log(error.statusCode)
  		console.log(error.message)
  	}
  }
  ```

  ```python Python theme={null}
  from timeback_case import CaseError, NotFoundError

  try:
      await client.documents.get("invalid-id")
  except NotFoundError:
      print("Document not found")
  except CaseError as error:
      print(error)
  ```
</CodeGroup>

## Configuration

<CodeGroup>
  ```typescript TypeScript theme={null}
  new CaseClient({
  	// Environment mode (Timeback APIs)
  	env: 'staging' | 'production',
  	auth: {
  		clientId: string,
  		clientSecret: string,
  	},

  	// OR Explicit mode (custom API)
  	baseUrl: string,
  	auth: {
  		clientId: string,
  		clientSecret: string,
  		authUrl: string,
  	},

  	// OR Provider mode (shared auth across clients)
  	provider: TimebackProvider,

  	// Optional
  	timeout?: number, // Request timeout in ms (default: 30000)
  })
  ```

  ```python Python theme={null}
  # Environment mode (Timeback APIs)
  client = CaseClient(
      env="staging",  # or "production"
      client_id="...",
      client_secret="...",
  )

  # Explicit mode (custom API)
  client = CaseClient(
      base_url="https://custom.example.com",
      auth_url="https://auth.example.com/oauth2/token",
      client_id="...",
      client_secret="...",
  )

  # Provider mode (shared auth)
  from timeback_common import TimebackProvider
  provider = TimebackProvider(env="staging", client_id="...", client_secret="...")
  client = CaseClient(provider=provider)

  # Optional
  # timeout: float = 30.0 (request timeout in seconds)
  ```
</CodeGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="CLR" icon="award" href="/beta/build-on-timeback/clients/clr">
    Comprehensive Learner Records
  </Card>

  <Card title="OneRoster" icon="users" href="/beta/build-on-timeback/clients/oneroster">
    Rostering and enrollments
  </Card>

  <Card title="Types" icon="brackets-curly" href="/beta/api-reference/overview">
    CASE type definitions
  </Card>
</CardGroup>
