LearnCard Documentation
GithubStatusSupportLaunch App
  • ๐Ÿš€Introduction
    • What is LearnCard?
    • Use Cases & Possibilities
    • Ecosystem Architecture
  • โšกQuick Start
    • Setup & Prerequisites
    • Your First Integration
  • ๐Ÿ“šTutorials
    • Create a Credential
    • Create a Boost
    • Create a ConsentFlow
    • Create a Connected Website
    • Send xAPI Statements
    • Listen to Webhooks
  • โœ…How-To Guides
    • Verify My Issuer
    • Connect Systems
      • Connect a Website
      • Connect a Game
    • Implement Flows
      • Claim Data after Guardian Consent
      • Connect via CHAPI
        • โญCHAPI Wallet Setup Guide
        • โ†”๏ธTranslating to CHAPI documentation
        • ๐Ÿ–ฅ๏ธDemo Application
        • ๐Ÿ”ฐUsing LearnCard to Interact with a CHAPI Wallet
        • ๐Ÿ“Cheat Sheets
          • Issuers
          • Wallets
    • Deploy Infrastructure
      • Remote Key Management
      • Generate API Tokens
      • Signing Authority
      • Connect to Independent Network
      • Build a Plugin
  • ๐Ÿ› ๏ธSDKs & API Reference
    • LearnCard Wallet SDK
      • Authentication
      • Usage Examples
      • SDK Reference
      • Plugin API Reference
      • Integration Strategies
      • Deployment
      • Troubleshooting
      • Changelog
    • LearnCloud Network API
      • Authentication
      • Usage Examples
      • Architecture
      • Notifications & Webhooks
      • Profiles
      • Profile Managers
      • Credentials
      • Boosts
      • Presentations
      • Storage
      • Contracts
      • DID Metadata
      • Claim Hooks
      • Auth Grants
      • Utilities
      • Models
      • OpenAPI
    • LearnCloud Storage API
      • Authentication
      • Usage Examples
      • Architecture
      • Storage
      • Index
      • User
      • Custom Storage
      • Utilities
      • Models
      • xAPI Reference
    • Plugins
      • Crypto
      • DIDKit
      • DID Key
      • Dynamic Loader
      • VC
        • Expiration Sub-Plugin
      • VC-Templates
      • VC-API
      • Ceramic
      • IDX
      • VPQR
      • Ethereum
      • CHAPI
      • LearnCard Network
      • LearnCloud
      • LearnCard
      • Simple Signing
      • Claimable Boosts
    • LearnCard CLI
  • ๐Ÿง Core Concepts
    • Identities & Keys
      • Decentralized Identifiers (DIDs)
      • Seed Phrases
      • Network Profiles
      • Signing Authorities
      • Trust Registries
    • Credentials & Data
      • Verifiable Credentials (VCs)
      • Credential Lifecycle
      • Schemas, Types, & Categories
      • Building Verifiable Credentials
      • Boost Credentials
      • Getting Started with Boosts
      • Credential URIs
      • xAPI Data
      • General Best Practices & Troubleshooting
    • Consent & Permissions
      • ConsentFlow Overview
      • Consent Contracts
      • User Consent & Terms
      • Consent Transactions
      • Auto-Boosts
      • Writing Consented Data
      • Accessing Consented Data
      • GameFlow Overview
    • Network & Interactions
      • Network Vision & Principles
      • Key Network Procedures
      • Core Interaction Workflows
    • Architecture & Principles
      • Control Planes
      • Plugin System
      • Auth Grants and API Tokens
  • ๐Ÿ”—Development
    • Contributing
Powered by GitBook
On this page
  • ๐Ÿ” Authentication
  • Storage
  • ๐Ÿ“ค Store a Credential or Presentation
  • ๐Ÿ“ฅ Resolve a Stored Item by URI
  • ๐Ÿ“ฆ Batch Resolve Stored Items
  • โœ… Quick Tips
  • Indexing
  • ๐Ÿงพ How the Index Stores Credential Metadata
  • ๐Ÿ“„ Get Credential Records Index
  • ๐Ÿ”ข Count Credential Records
  • โž• Add a Credential Record
  • โž•โž• Add Many Credential Records
  • โœ๏ธ Update a Credential Record
  • โŒ Delete a Credential Record
  • ๐Ÿงน Delete All Credential Records
  • xAPI Statements
  • ๐Ÿ“ค Send an xAPI Statement
  • โœ… Best Practices
  • ๐Ÿ“– Read xAPI Statements

Was this helpful?

  1. SDKs & API Reference
  2. LearnCloud Storage API

Usage Examples

This page provides common usage examples for the LearnCloud Storage API, so you can quickly see how to store, retrieve, and manage verifiable data objects like credentials, presentations, and metadata.

Each example is standalone and self-explanatory. Scroll, copy, and paste what you need.

โœ… All examples assume:

  • You have a valid LearnCloud JWT (via auth or delegation)

  • Youโ€™re storing data on behalf of a user identified by a DID

  • Youโ€™re using the endpoint: https://cloud.learncard.com/api


๐Ÿ” Authentication

All requests require:

  • Authorization: Bearer <your-JWT>

  • The JWT must resolve to a DID matching the stored object owner, unless delegated.


Storage

๐Ÿ“ค Store a Credential or Presentation

Endpoint: POST https://cloud.learncard.com/storage/store Description: Stores a VC, VP, or JWE, and returns a lc:cloud:cloud.learncard.com/trpc:credential:id URI for later access.

Request

POST /storage/store
Authorization: Bearer <JWT>
Content-Type: application/json

{
  "item": {
    "@context": ["https://www.w3.org/2018/credentials/v1"],
    "type": ["VerifiableCredential"],
    "issuer": "did:key:xyz...",
    "credentialSubject": {
      "id": "did:key:abc...",
      "achievement": "Quickstart Achievement"
    }
  }
}

Response

"lc:cloud:cloud.learncard.com/trpc:credential:1234"

๐Ÿ“ฅ Resolve a Stored Item by URI

Endpoint: GET https://cloud.learncard.com/storage/resolve/{uri} Description: Fetches an encrypted, stored credential or presentation from its URI.

Request

GET /storage/resolve/lc:cloud:cloud.learncard.com/trpc:credential:1234
Authorization: Bearer <JWT>

Response

{
  "protected": "eyJlbmMiOiJ...",
  "recipients": [...],
  "iv": "...",
  "ciphertext": "...",
  "tag": "..."
}

Returns the encrypted JWE for the stored credential or presentation.


๐Ÿ“ฆ Batch Resolve Stored Items

Endpoint: POST https://cloud.learncard.com/storage/resolve/batch Description: Resolves multiple credential URIs in a single call.

Request

POST /storage/resolve/batch
Authorization: Bearer <JWT>
Content-Type: application/json

{
  "uris": [
    "lc:cloud:cloud.learncard.com/trpc:credential:1",
    "lc:cloud:cloud.learncard.com/trpc:credential:2",
    "lc:cloud:cloud.learncard.com/trpc:credential:3"
  ]
}

Response

[
  {
    "protected": "...",
    "recipients": [...],
    "iv": "...",
    "ciphertext": "...",
    "tag": "..."
  },
  {
    "protected": "...",
    "recipients": [...],
    "iv": "...",
    "ciphertext": "...",
    "tag": "..."
  },
  null
]

โš ๏ธ If a URI is invalid or not found, null is returned for that entry.


โœ… Quick Tips

Action
Endpoint
Method
Returns

Store VC/VP/JWE

/storage/store

POST

URI of stored item

Resolve 1 item

/storage/resolve/{uri}

GET

Encrypted JWE

Resolve many

/storage/resolve/batch

POST

Array of JWE/null results

  • Always encrypt before sending to the API

  • Use returned URIs as persistent identifiers

  • Batch resolve to reduce network overhead

Indexing

๐Ÿงพ How the Index Stores Credential Metadata

The LearnCard index is used to securely store metadata about credentials youโ€™ve issued or received. It doesn't store the full credential itselfโ€”instead, it stores a reference (uri) to the credential (which is stored separately in LearnCloud Storage) along with optional metadata fields.

Each entry is encrypted as a JWE object and stored as a CredentialRecord.

Unencrypted Format (Before Encryption)

type CredentialRecord = {
  id: string;            // Internal ID
  uri: string;           // Reference to the stored credential (e.g. "lc:cloud:cloud.learncard.com/trpc:credential:1234")
  [key: string]: any;    // Custom metadata fields (e.g. tags, labels, timestamps)
}

Encrypted Format (Submitted to /index/add, etc.)

{
  encryptedRecord: { /* JWE */ },
  fields: ["uri", "tags", "created"] // Metadata fields included in the encrypted payload
}

This encrypted object becomes the payload for the index API (e.g., /index/add, /index/get). LearnCard decrypts it on read and uses it to support search, sync, and filtered queriesโ€”without exposing sensitive data.

๐Ÿ“„ Get Credential Records Index

Endpoint: POST https://cloud.learncard.com/index/get Description: Query your CredentialRecords index with pagination, sorting, and optional filtering.

Request

POST /index/get
Authorization: Bearer <JWT>
Content-Type: application/json
{
  "limit": 25,
  "query": { "type": "EducationCredential" },
  "encrypt": true,
  "sort": "newestFirst",
  "includeAssociatedDids": true
}

Response

{
  "records": [
    {
      "id": "abc123",
      "did": "did:key:xyz...",
      "cursor": "cursorValue",
      "created": "2024-05-01T12:00:00Z",
      "modified": "2024-05-01T12:00:00Z"
    }
  ],
  "hasMore": false,
  "cursor": "cursorValue"
}

Returns a paginated list of credential records or a JWE if encryption is enabled.


๐Ÿ”ข Count Credential Records

Endpoint: POST https://cloud.learncard.com/index/count Description: Count the number of credential records matching a query.

Request

POST /index/count
Authorization: Bearer <JWT>
Content-Type: application/json
{
  "query": { "type": "EducationCredential" },
  "encrypt": true,
  "includeAssociatedDids": true
}

Response

42

or

{ /* JWE object */ }

Returns either a plain number or encrypted JWE containing the count.


โž• Add a Credential Record

Endpoint: POST https://cloud.learncard.com/index/add Description: Add a new credential record to the user's index.

Request

POST /index/add
Authorization: Bearer <JWT>
Content-Type: application/json
{
  "record": { /* JWE object + fields: z.string().array() */ }
}

Response

true

Returns true if the record was successfully added.


โž•โž• Add Many Credential Records

Endpoint: POST https://cloud.learncard.com/index/addMany Description: Add multiple credential records in one request.

Request

POST /index/addMany
Authorization: Bearer <JWT>
Content-Type: application/json
{
  "records": [
    {
      /* JWE object */
    },
    {
      /* JWE object */
    }
  ]
}

Response

true

Returns true if all records were successfully added.


โœ๏ธ Update a Credential Record

Endpoint: PATCH https://cloud.learncard.com/index/{id} Description: Update a credential record by its internal ID.

Request

PATCH /index/abc123
Authorization: Bearer <JWT>
Content-Type: application/json
{
  "id": "abc123",
  "updates": {
    /* JWE object updates */
  }
}

Response

true

Returns true if the record was successfully updated.


โŒ Delete a Credential Record

Endpoint: DELETE https://cloud.learncard.com/index/{id} Description: Delete a specific credential record by ID.

Request

DELETE /index/abc123
Authorization: Bearer <JWT>

Response

true

Returns true if the record was deleted.


๐Ÿงน Delete All Credential Records

Endpoint: DELETE https://cloud.learncard.com/index Description: Delete all credential records for the authenticated user.

Request

DELETE /index
Authorization: Bearer <JWT>

Response

true

Returns true if all records were removed.

xAPI Statements

๐Ÿ“ค Send an xAPI Statement

Endpoint: POST https://cloud.learncard.com/xapi/statements Description: Send a structured learning event to LearnCloud, using the "Actor - Verb - Object" format.

Request

POST /xapi/statements
Authorization: Bearer <JWT>
X-Experience-API-Version: 1.0.3
X-VP: <JWT>
Content-Type: application/json
{
  "actor": {
    "objectType": "Agent",
    "name": "did:key:abc123...",
    "account": {
      "homePage": "https://www.w3.org/TR/did-core/",
      "name": "did:key:abc123..."
    }
  },
  "verb": {
    "id": "http://adlnet.gov/expapi/verbs/attempted",
    "display": {
      "en-US": "attempted"
    }
  },
  "object": {
    "id": "http://yourgame.com/activities/level-1-challenge",
    "definition": {
      "name": { "en-US": "Level 1 Challenge" },
      "description": { "en-US": "First challenge of the game" },
      "type": "http://adlnet.gov/expapi/activities/simulation"
    }
  }
}

Returns 200 OK on success. No response body is returned unless there is an error.

โœ… Best Practices

  • Use the same DID in actor.name and actor.account.name

  • Always set X-VP header with your JWT

  • Use standard verbs like attempted, completed, mastered, demonstrated

  • Use real or resolvable URIs for object.id (or consistent mock URLs)

  • Test with dummy statements before production

๐Ÿ“– Read xAPI Statements

Endpoint: GET https://cloud.learncard.com/xapi/statements Description: Retrieve xAPI statements associated with the authenticated user's DID.


๐Ÿ” Basic Query

GET /xapi/statements?agent={actor}
Authorization: Bearer <JWT>
X-Experience-API-Version: 1.0.3
X-VP: <JWT>
const actor = {
  objectType: "Agent",
  name: userDid,
  account: {
    homePage: "https://www.w3.org/TR/did-core/",
    name: userDid
  }
};

const query = new URLSearchParams({
  agent: JSON.stringify(actor)
});

const response = await fetch(`https://cloud.learncard.com/xapi/statements?${query}`, {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json',
    'X-Experience-API-Version': '1.0.3',
    'X-VP': jwt
  }
});

const data = await response.json();

Returns a list of xAPI statements for the user.


๐Ÿ”Ž Filtered Query Example

GET /xapi/statements?agent={...}&verb={...}&since={...}
const filterParams = new URLSearchParams({
  agent: JSON.stringify(actor),
  verb: "http://adlnet.gov/expapi/verbs/completed",
  since: "2024-03-01T00:00:00Z",
  until: "2024-03-31T23:59:59Z",
  limit: "10"
});

const response = await fetch(`https://cloud.learncard.com/xapi/statements?${filterParams}`, {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json',
    'X-Experience-API-Version': '1.0.3',
    'X-VP': jwt
  }
});

Filters results by verb and date range.


๐Ÿ” Paginated Fetching

let more = "";
const getPage = async (moreUrl = "") => {
  const url = moreUrl || `https://cloud.learncard.com/xapi/statements?${filterParams.toString()}`;
  const response = await fetch(url, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
      'X-Experience-API-Version': '1.0.3',
      'X-VP': jwt
    }
  });
  const data = await response.json();
  processStatements(data.statements);
  return data.more || null;
};

more = await getPage();
while (more) {
  more = await getPage(more);
}

๐Ÿงช Security Notes

  • โœ… You can only query your own statements unless using delegated access.

  • โŒ A 401 means:

    • Invalid or expired JWT

    • DID mismatch between JWT and actor

    • Missing X-VP header


๐Ÿค Delegated Access

To allow another party to query your statements:

  1. Issue a delegate credential

  2. Wrap it in a presentation

  3. Sign it into a JWT

  4. Use it in the X-VP header

const delegateCredential = await userA.invoke.issueCredential({
  type: 'delegate',
  subject: userB.id.did(),
  access: ['read']
});

const unsignedPresentation = await userB.invoke.newPresentation(delegateCredential);
const delegateJwt = await userB.invoke.issuePresentation(unsignedPresentation, {
  proofPurpose: 'authentication',
  proofFormat: 'jwt'
});

๐Ÿงผ Voiding a Statement

You can void a previously sent statement by its ID.

const voidStatement = {
  actor,
  verb: {
    id: "http://adlnet.gov/expapi/verbs/voided",
    display: { "en-US": "voided" }
  },
  object: {
    objectType: "StatementRef",
    id: "original-statement-id"
  }
};

await fetch(`https://cloud.learncard.com/xapi/statements`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Experience-API-Version': '1.0.3',
    'X-VP': jwt
  },
  body: JSON.stringify(voidStatement)
});

โš ๏ธ You can only void statements you originally submitted.


๐Ÿง  Best Practices for Querying

  • Use limit, since, and verb to keep results efficient

  • Use activity, ascending, or format=ids for advanced querying

PreviousAuthenticationNextArchitecture

Last updated 17 days ago

Was this helpful?

๐Ÿ› ๏ธ