Skip to main content
By the end of this guide you will have a working Tally tenant, a submitted identity snapshot for a business entity, and a response showing that entity’s current verified state. Every example uses the REST API, and the final step shows you how to do the same thing with the TypeScript SDK.
1

Obtain an access token

Tally uses OIDC for authentication. Before making any API call, obtain a signed JWT from your OIDC provider (for example, via a client credentials grant, a user sign-in flow, or a service account token). Pass the token in every request as an Authorization: Bearer header.
# Set your token in an environment variable so you can reuse it below
export TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Tally derives your principal ID from the token claims using the format oidc:{issuer_url}#{sub}. You need to add this principal as a member of your tenant before making tenant-scoped calls — you’ll do that automatically in the next step when you create the tenant with authentication enabled.
See Authentication for a full explanation of token requirements, principal IDs, and error codes.
2

Create a tenant

A tenant is your organizational workspace. Create one by posting a tenant_id and a human-readable name. When authentication is enabled, your principal is automatically added as tenant_owner.
curl -X POST https://api.example.com/v1/tenants \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "tenant_id": "my-org",
    "name": "My Organization"
  }'
The tenant_id must be unique across all tenants and cannot be changed after creation. Choose something URL-safe and meaningful, such as your company slug.
3

Submit your first snapshot

Snapshots are submitted to the entity-states endpoint. The body is an entity state envelope — a structured document that describes the subject’s identity attributes, the evidence that supports them, and an audit trail.For a first snapshot, snapshot_version must be 1. Subsequent updates increment this value automatically.
curl -X POST https://api.example.com/v1/tenants/my-org/entity-states \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "envelope_version": "entity_state_envelope_v1",
    "snapshot_id": "550e8400-e29b-41d4-a716-446655440000",
    "snapshot_version": 1,
    "generated_at": "2024-01-15T10:00:00Z",
    "subject": {
      "subject_type": "entity",
      "subject_id": "ent_acme_001"
    },
    "attributes": {
      "legal_name": "Acme Industrial Supply, Inc.",
      "entity_kind": "organization",
      "formation_jurisdiction_code": "US-DE",
      "entity_status": "active"
    },
    "evidence": [],
    "audit": {
      "created_by": "kyc_service",
      "created_at": "2024-01-15T10:00:00Z",
      "source": "manual_entry"
    }
  }'
Tally stores this envelope immutably, computes its SHA-256 hash after RFC 8785 canonicalization, and records it as version 1 in the subject’s snapshot chain. The subject_id (ent_acme_001) is now permanently associated with your tenant.
4

Read the subject's current state

Retrieve the latest verified state for your subject at any time. The response includes the current identity attributes, a provenance summary, and the hash of the most recent snapshot.
curl https://api.example.com/v1/tenants/my-org/subjects/entity/ent_acme_001 \
  -H "Authorization: Bearer $TOKEN"
The prev_hash is null because this is the first snapshot. Once you submit a second snapshot, both hashes will be populated, forming the beginning of the chain.
5

Try the TypeScript SDK

The @tally/sdk package wraps every REST call with a fully-typed client. Install it, then replicate the create and read steps above in a few lines of TypeScript.
npm install @tally/sdk
import { createClient } from '@tally/sdk';

const client = createClient({
  baseUrl: 'https://api.example.com',
  token: 'YOUR_TOKEN',
});

// Submit a snapshot
await client.entityStates.create(
  {
    envelope_version: 'entity_state_envelope_v1',
    snapshot_id: '550e8400-e29b-41d4-a716-446655440000',
    snapshot_version: 1,
    generated_at: '2024-01-15T10:00:00Z',
    subject: { subject_type: 'entity', subject_id: 'ent_acme_001' },
    attributes: {
      legal_name: 'Acme Industrial Supply, Inc.',
      entity_kind: 'organization',
      formation_jurisdiction_code: 'US-DE',
      entity_status: 'active',
    },
    evidence: [],
    audit: {
      created_by: 'kyc_service',
      created_at: '2024-01-15T10:00:00Z',
      source: 'manual_entry',
    },
  },
  { tenantId: 'my-org' }
);

// Read the subject's current state
const current = await client.subjects.getCurrent(
  'entity',
  'ent_acme_001',
  { tenantId: 'my-org' }
);

console.log(current.identity);
// { legal_name: 'Acme Industrial Supply, Inc.', ... }
The client is tree-shakeable and runs in Node.js 18+ and modern browsers. All methods are typed against the same request and response shapes the REST API uses, so your IDE gives you autocomplete and compile-time safety.
Before building a production integration, read the Core Concepts section. Understanding how subjects, snapshots, grants, and roles interact will save you significant debugging time — especially around tenant membership requirements and grant scopes.