# What is LearnCard?

[**LearnCard**](https://www.learncard.com) is a modular, open-source wallet and SDK designed to give learners and workers control over their credentials, achievements, and learning data. Built on open standards like [W3C Verifiable Credentials](/core-concepts/credentials-and-data/verifiable-credentials-vcs) and [Decentralized Identifiers (DIDs)](/core-concepts/identities-and-keys/decentralized-identifiers-dids), LearnCard enables privacy-respecting, consent-based data sharing across educational and employment ecosystems.

Whether you're building edtech apps, issuing credentials, or designing learner-focused dashboards, LearnCard offers the foundation and flexibility to integrate trustworthy, portable data.

[**LearnCard**](https://www.learncard.com) is maintained by [**Learning Economy Foundation**](https://www.learningeconomy.io).

***

### Why Use LearnCard?

* **Portable**: Credentials travel with the learner—not locked into one system.
* **Consent-Driven**: Learners control who sees their data, and when.
* **Open**: Built on interoperable, community-driven standards.
* **Developer-Friendly**: Plug in easily to existing apps with modern APIs and tools.

***

### What You Can Build With It

LearnCard is already used to:

* Issue and verify credentials (skills, achievements, badges)
* Build learner passports and skill profiles
* Power AI copilots and bots that adapt to learner data
* Create consent-based dashboards for families, schools, and career counselors
* Support interoperability across educational, employment, and credentialing platforms

***

### Docs Structure at a Glance

This documentation is your guide to building with LearnCard. Here’s how to navigate it:

* [**Quick Start**](/quick-start/your-first-integration): Go from zero to integrated in minutes.
* [**Tutorials**](/tutorials/create-a-credential): Learn by building — create credentials, boosts, and connected apps.
* [**How-To Guides**](/how-to-guides/send-credentials): Step-by-step recipes for common workflows.
* [**Core Concepts**](/core-concepts/credentials-and-data/verifiable-credentials-vcs): Understand credentials, DIDs, consent, and the plugin system.
* [**SDK Reference**](/sdks/learncard-core): Deep dive into our SDK and APIs.

If you're new, start with [**Your First Integration**](/quick-start/your-first-integration) under the Quick Start section.

***

### How to Implement LearnCard

1. **Pick your flow**: Issue credentials, verify them, or manage a user's wallet.
2. **Install the SDK**:

   ```bash
   npm install @learncard/init
   ```
3. **Use open standards**: LearnCard supports [Open Badges 3.0](https://www.imsglobal.org/spec/ob/v3p0/), W3C Verifiable Credentials, and more.
4. **Scale up**: Add consent flows, network features, and advanced cryptography as needed.

Whether you're building a web app, mobile experience, backend service, or bot, LearnCard has the primitives and integrations to make it seamless.

***

### You're in Good Hands

This stack was built by developers, for developers. It supports open standards and real-world interoperability from day one. We’re glad you’re here—let’s build a more learner-friendly future together.

### Questions or Feedback?

The best way to start engaging in the community is to participate in our [Github Discussions](https://github.com/learningeconomy/LearnCard/discussions):

* [Post a Feature Request ](https://github.com/learningeconomy/LearnCard/discussions/categories/feature-requests)💡
* [Ask for Help](https://github.com/learningeconomy/LearnCard/discussions/categories/help) 💖
* [Show off your project to the community!](https://github.com/learningeconomy/LearnCard/discussions/categories/show-and-tell) 🙌

Do you need custom development or technical support? Click [here](https://www.learningeconomy.io/contact), or send us an email at <sdk@learningeconomy.io>.


# Use Cases & Possibilities

LearnCard helps you build apps where learners, workers, and systems can issue, store, share, and verify data they own and control.

Below are the most common use cases. These are real things being built today with LearnCard—feel free to borrow, remix, or extend.

***

### 🎓 Education & Credentialing

**Issue and manage credentials**:

* Issue diplomas, badges, transcripts, and skill certificates as W3C Verifiable Credentials
* Use Boosts for richer metadata and credential display
* Attach PDFs or supporting documents and evidence to any credential

**Verify learner achievements**:

* Accept credential submissions from any LearnCard-compatible wallet
* Validate credential integrity with zero infrastructure
* Build dashboards or reports using trusted, portable data

***

### 🧠 Learning Bots & AI Copilots

**Personalize educational experiences**:

* Translate LER data into embeddings to track semantic context and learner history
* Adapt learning content and feedback in real-time based on a learner's full portfolio
* Enable bots to "remember" user milestones and feedback across sessions

**Build secure bots**:

* Ask for granular, auditable consent with ConsentFlow
* Protect learner data using ZKPs and selective disclosure

**Agent-to-Agent credentials:**

* Create portable credentials for your AI agents to prove their skills and experiences
* Build trust for your agents with a wallet of certifications they can access and use

***

### 🕹️ Games & Learning Apps

**Gamify credentials**:

* Issue Boosts and badges when players complete challenges
* Store achievements in a secure LearnCard wallet
* Let players claim credentials via QR or in-game triggers

**Integrate GameFlow**:

* Connect educational games directly to LearnCard
* Sync player progress with wallet credentials
* Add guardianship and consent for younger learners

***

### 🧾 Job Market & Workforce

**Build skill-based portfolios**:

* Allow users to store credentials from bootcamps, apprenticeships, employers
* Translate credentials into job-relevant summaries or skill graphs
* Share verifiable presentations with employers or HR tools

**Streamline hiring pipelines**:

* Accept trusted, tamper-proof credentials at application
* Automate pre-verification using LearnCard APIs

***

### 🧑‍👩‍👧 Families & Guardianship

**Enable parents to manage data**:

* Assign guardianship rights over a child’s LearnCard wallet
* Control what data can be shared, when, and with whom
* Get alerts and logs for all data usage

**Build dashboards for families**:

* Show learning progress across apps and institutions
* Visualize credentials earned over time

***

### 🏢 Institutions & Organizations

**Run credentialing programs**:

* Create issuing workflows with custom rules via Signing Authorities
* Use LearnCard Network for easy peer-to-peer credential exchange
* Host a custom credential viewer or verification portal

**Enable internal verification**:

* Issue internal training credentials
* Use presentations for access control or role validation

***

### 🤝 Ecosystem Interoperability

**Bridge systems**:

* Translate and sync data using standards like OBv3, CLR, LTI, and LIF
* Build adapters that pull and push data from LearnCard into LMSs, CRMs, or SISs

**Use DID Authentication**:

* Enable passwordless, portable login to your application

***

### 🛠️ Build Your Own Flow

The LearnCard SDK and APIs are modular—combine any of the following:

* Wallets and identity (DIDs)
* Credential issuing and verification
* Consent and access control
* Secure storage (LearnCloud)
* Peer-to-peer sharing (via QR, links, or APIs)
* AI integrations (memory + prompts + embeddings)

***

### ✅ Start Building

If you’re ready to see it in action:

* 🔌 Go to [Your First Integration](/quick-start/your-first-integration)
* 🧰 Dive into the [SDK Reference](/sdks/learncard-core)
* 🧠 Revisit[ Core Concepts](/core-concepts/identities-and-keys) if you want a deeper primer


# Ecosystem Architecture

How LearnCard and LearnCloud fit together, and how they interoperate with the world.

**LearnCard** is the lifelong-learning passport — the wallet, app, CLI, and SDK a learner (or any app acting on their behalf) uses to **collect, understand, and navigate** their learning and employment record. **LearnCloud** is the open API platform behind it: a network for sending and receiving credentials, encrypted personal storage, and an AI layer that turns the passport into something useful.

Together they make verifiable learning records portable across schools, states, employers, games, and AI — without locking anyone into a closed ecosystem.

This is the single page to understand the whole stack.

***

## Three verbs

Three verbs the whole platform is organized around:

**Collect** — fill your passport from every source. School transcripts, course completions, badges, internships, work history, certifications — credentials aggregate longitudinally, across institutions, products, and life stages.

**Understand** — make sense of what's inside. Skills extracted from your record, gaps identified against your goals, AI tutoring grounded in your actual history. Insights you can act on.

**Navigate** — turn your record into opportunity. Pathways into education, jobs that fit your skills, scholarships and credentials that unlock them, AI agents that can advocate on your behalf with your consent.

Every component below — LearnCard, LearnCloud, the standards core — exists to make these three verbs portable, open, and learner-controlled.

***

## At a glance

```mermaid
%%{init: {"flowchart": {"defaultRenderer": "elk"}}}%%
flowchart LR
  classDef indigo stroke:#6366f1,stroke-width:2px,fill:transparent
  classDef teal   stroke:#14b8a6,stroke-width:2px,fill:transparent
  classDef violet stroke:#8b5cf6,stroke-width:2px,fill:transparent
  classDef orange stroke:#f97316,stroke-width:2px,fill:transparent

  Eco["Schools · States · Employers · EdTech · Games · AI Agents · Partner Apps"]:::orange

  subgraph LC["LearnCard · clients & SDK"]
    direction TB
    APP["LearnCard App · CLI"]
    SDK["Wallet SDK<br/><i>modular plugins · Rust ↔ WASM core</i>"]
  end
  class LC,APP,SDK teal

  subgraph CL["LearnCloud · open API platform"]
    direction TB
    NET["Network API<br/><i>send · share · verify · consent</i>"]
    STR["Storage API<br/><i>store · read · delete · encrypt</i>"]
    AI["AI API<br/><i>ingest · assess · analyze ·<br/>tutor · award · MCP</i>"]
  end
  class CL,NET,STR,AI indigo

  subgraph CORE["Standards-Based Core"]
    direction TB
    Std["W3C VCs · DIDs · OBv3 · CLR 2.0<br/>VC-API · OIDC4VC · CHAPI · Ed25519"]
  end
  class CORE,Std violet

  Eco --> LC
  LC <==>|"tRPC + REST · OpenAPI"| CL
  LC -.- CORE
  CL -.- CORE
```

**LearnCard** is everything users (or partners on their behalf) actually touch. Wallet operations — signing, verification, DID resolution, JSON-LD canonicalization — run in a Rust core compiled to native and WebAssembly, so the same SDK works identically on the web, iOS, Android, and Node.

**LearnCloud** is three open APIs, each fully OpenAPI-documented and individually adoptable. They sit behind one network of verifiable, consented data.

The **standards core** is what makes both halves interoperable — with each other, with any conformant wallet, and with any conformant verifier in the world.

***

## LearnCard · clients & SDK

What a learner — or any app integrating on their behalf — actually touches.

| Surface           | Role                                                          |
| ----------------- | ------------------------------------------------------------- |
| **LearnCard App** | Universal wallet — web, iOS, Android                          |
| **LearnCard CLI** | Automation, scripting, server-side use                        |
| **Wallet SDK**    | `@learncard/init` for programmatic control, plugin-extensible |

The SDK is **modular**. Identity providers, signing methods, storage backends, exchange protocols, AI providers — every concern is a plugin you can swap. The same SDK works identically across the web, iOS, Android, and Node because the credential primitives (signing, verification, DID resolution, JSON-LD canonicalization) live in a Rust core compiled to native and WebAssembly.

This means apps don't have to choose between speed and portability — they get both, on every surface.

→ Deep dives: [Wallet SDK](/sdks/learncard-core) · [Plugin System](/core-concepts/architecture-and-principles/plugins) · [Control Planes](/core-concepts/architecture-and-principles/control-planes)

***

## LearnCloud · open API platform

Three open APIs. Each fully OpenAPI-documented. Each individually adoptable — use one, two, or all three.

### Network API

The credential exchange backbone. Send credentials to learners, run consent contracts, register Signing Authorities, manage trust between profiles, query the credential graph. Backed by a graph database; every interaction is recorded and revocable.

`send · share · verify · consent · revoke`

→ [LearnCloud Network API](/sdks/learncard-network) · [OpenAPI](https://network.learncard.com/docs)

### Storage API

End-to-end encrypted personal credential storage, with cross-device sync and pluggable storage backends. Credentials are encrypted client-side; the platform never sees private data unless explicitly shared via a consent contract.

`store · read · delete · encrypt`

→ [LearnCloud Storage API](/sdks/learncloud-storage-api)

### AI API

The passport is data. The AI API turns that data into something useful, on demand, across every product the learner uses.

* **Ingest** — CLRs, transcripts, badges, and work history flow into a unified learner context — a single source of truth the AI grounds every interaction in.
* **Assess** — Skill assessments calibrated against the learner's actual record, not generic rubrics.
* **Analyze** — Insights, skill gaps, and goal mapping against frameworks like CTDL, O\*NET, and ESCO.
* **Tutor** — AI tutors that know the learner — their goals, their history, their level — instead of starting from a blank prompt every session.
* **Award** — Auto-issue credentials when assessment criteria are met, closing the loop from learning to recognition.
* **MCP** — A Model Context Protocol server that exposes learner context to external AI agents with explicit, scoped consent.

Because the learner context is a first-class API, **AI sessions are portable**: a tutoring relationship that begins in one product can continue in another, with the learner's history and consent intact.

{% hint style="info" %}
Every AI API call against learner data is gated by an explicit consent contract. The platform never operates on a learner's record without their (or their guardian's) approval.
{% endhint %}

***

## How the stack stays interoperable

LearnCard and LearnCloud both build on a layered standards core. Each layer is independent — you can swap one without rewriting the others.

```mermaid
%%{init: {"flowchart": {"defaultRenderer": "elk"}}}%%
flowchart LR
  L1["1 · Identity & Crypto<br/>did:key · did:web · plus any DID method via plugin<br/>Ed25519 · Secp256k1 — Rust ↔ WASM core"]:::indigo
  L2["2 · Envelopes<br/>W3C Verifiable Credentials 2.0 (VC 1.1 also supported)<br/>Verifiable Presentations · JWT or LD-Proof"]:::teal
  L3["3 · Schemas<br/>Open Badges 3.0 · CLR 2.0 · IEEE LER<br/>plus custom JSON-LD"]:::violet
  L4["4 · Skills & Alignment<br/>CTDL (via ctid) · CASE · O*NET · ESCO<br/>OpenSALT skill frameworks"]:::orange
  L5["5 · Exchange<br/>VC-API · OIDC4VC (OID4VP / OID4VCI)<br/>CHAPI · VPQR · claim links · inbox"]:::fuchsia
  L6["6 · Trust & Consent<br/>Signing Authorities · ConsentFlow contracts<br/>Trust registries"]:::green

  L1 --> L2 --> L3 --> L4 --> L5 --> L6

  classDef indigo  stroke:#6366f1,stroke-width:2px,fill:transparent;
  classDef teal    stroke:#14b8a6,stroke-width:2px,fill:transparent;
  classDef violet  stroke:#8b5cf6,stroke-width:2px,fill:transparent;
  classDef orange  stroke:#f97316,stroke-width:2px,fill:transparent;
  classDef fuchsia stroke:#d946ef,stroke-width:2px,fill:transparent;
  classDef green   stroke:#22c55e,stroke-width:2px,fill:transparent;
```

| Layer              | Today                                                    | Status            |
| ------------------ | -------------------------------------------------------- | ----------------- |
| Identity           | did:key · did:web · plus any DID method via plugin       | First-class       |
| Crypto             | Ed25519, Secp256k1 — Rust ↔ WASM core                    | First-class       |
| Envelopes          | W3C VC 2.0 (and VC 1.1)                                  | First-class       |
| Schemas            | OBv3, CLR 2.0, IEEE LER, custom JSON-LD                  | First-class       |
| Skills & alignment | CTDL via `ctid`; CASE / O\*NET / ESCO via Alignment URLs | First-class       |
| Exchange           | VC-API, OIDC4VC, CHAPI, VPQR, claim links, inbox         | Each has a plugin |
| Trust              | Signing Authorities, ConsentFlow contracts, Registries   | First-class       |

### Partners interoperate via standards, not custom integration code

The plugin and app-store layers are how the rest of the world plugs in *without* changes to the core:

* **Plugin layer** — protocol-level integrations. Custom DID methods, custom signing, custom credential types, custom AI providers, custom storage backends. A partner like an **Open Awarding Service** can issue credentials into LearnCard by speaking VC-API. **LIF** can map data into the network through its own JSON-LD context. **SCD** consumers can render credentials from any provider that publishes the right metadata. **KYC** providers can attach identity proofs as endorsements without touching the credential subject. None of these require code in this repo.
* **App store** — application-level integrations. Partner apps embed LearnCard (or are embedded by it) and exchange credentials through the **Partner Connect SDK** with origin-validated postMessage. The app store is the front door for the broader ecosystem of products learners actually use.

This is the same model that makes any conformant wallet — DCC, MATTR, Procivis, Microsoft Entra Verified ID — readable by LearnCard verifiers and vice versa. Standards are the wire; plugins and the app store are the connectors.

{% hint style="success" %}
**Plug in via standards, not custom code.** A partner that publishes a conformant Verifiable Credential is already interoperable with LearnCard — no special integration required.
{% endhint %}

→ Deep dives: [Verifiable Credentials](/core-concepts/credentials-and-data/verifiable-credentials-vcs) · [DIDs](/core-concepts/identities-and-keys/decentralized-identifiers-dids) · [Skill Frameworks & OpenSALT](/sdks/learncard-network/skills-and-opensalt) · [Partner Connect SDK](/sdks/partner-connect) · [Interoperability](/introduction/interoperability)

***

## How a credential moves through the system

Three actors, one shared protocol. (Plus a guardian, when the holder is a minor.)

```mermaid
sequenceDiagram
  autonumber
  actor Issuer as Issuer<br/>school · state · partner
  actor Holder as Holder<br/>learner · LearnCard
  actor Guardian as Guardian<br/>if minor
  actor Verifier as Verifier<br/>college · employer · AI agent

  Note over Issuer: authenticates learner via<br/>their existing system<br/>(Clever · district SSO ·<br/>Google Workspace · Entra · etc.)
  Issuer->>Holder: sign credential bound to learner DID<br/>(Boost / OBv3 / CLR 2.0)
  opt Holder is a minor
    Issuer->>Guardian: request approval
    Guardian-->>Holder: approve
  end
  Holder->>Holder: store encrypted<br/>(Storage API)
  Verifier->>Holder: request presentation<br/>(Partner Connect · OID4VP · CHAPI · MCP)
  Holder->>Verifier: signed Verifiable Presentation<br/>(holder DID = proof of ownership)
  Verifier->>Verifier: verify signatures · resolve DIDs ·<br/>check status & trust
```

In a typical K-12 or workforce flow:

1. The **issuer** (school, state, employer, EdTech app) authenticates the learner with their existing identity system, then signs a credential bound to the learner's DID and sends it.
2. The **holder** (the learner, or their guardian if a minor) receives it through a claim link, an inbox, or a direct send. The wallet stores it encrypted in LearnCloud Storage; the network records it as received.
3. The **verifier** (college, employer, scholarship platform, AI agent) requests credentials via Partner Connect, VC-API, OID4VP, CHAPI, or MCP. The holder approves with selective disclosure, and the wallet returns a signed Verifiable Presentation.

### Identity providers, holder binding, and guardianship

Authentication shows up in two places, deliberately decoupled:

**On the wallet side (the personal passport)** — LearnCard uses a *modular auth provider* model. Today: email/phone. Provider model supports any OIDC-compliant provider, Keycloak, Okta, custom. Every wallet has a learner DID; private-key custody uses Shamir Secret Sharing across device + server, with passkey, recovery phrase, and email backup options.

**On the issuer side (schools, states, ecosystem actors)** — issuers use *whatever auth they already have*. LearnCard does not require — and never will require — schools or states to adopt a particular identity system. Whatever they use to authenticate the learner today, they can use to gate credential issuance tomorrow. The learner's DID is just the address the credential is sent to.

**Holder binding** is cryptographic — to share a credential, the holder signs a Verifiable Presentation with the private key of the DID the credential is bound to. Possession of the key is proof of identity.

**Guardian gating** is supported via approval tokens and a `guardianStatus` field on inbox credentials, used when the holder is a minor or when the issuer requires guardian co-signature before a credential can be claimed.

→ Deep dives: [Auth Coordinator](/core-concepts/architecture-and-principles/auth-coordinator) · [Signing Authorities](/core-concepts/identities-and-keys/signing-authorities) · [Trust Registries](/core-concepts/identities-and-keys/trust-registries) · [Universal Inbox](/core-concepts/network-and-interactions/universal-inbox) · [Guardian-Gated Credentials](/how-to-guides/implement-flows/guardian-gated-credentials) · [ConsentFlow Overview](/core-concepts/consent-and-permissions/consentflow-overview)

***

## What you can build with it

The components compose upward into real products:

1. **Data Pipes** — ingest credential and learning data
2. **Portfolios** — aggregate credentials per learner / worker
3. **Consent** — share with granular control
4. **Credentialing** — issue and verify new claims
5. **Analytics** — insight from usage and pathways
6. **Personalization** — adapt experiences in real time
7. **Pathways** — discover careers and learning options
8. **Applications** — features people see and touch

You don't need every layer. Most teams start with one and grow into others.

***

## Next steps

If you're...

* **Building an app** → start with the [Wallet SDK](/sdks/learncard-core)
* **Working cloud-side** → start with the [Network API](/sdks/learncard-network) or [Storage API](/sdks/learncloud-storage-api)
* **Issuing credentials** → start with [Boost Credentials](/core-concepts/credentials-and-data/boost-credentials)
* **Building consent flows** → start with [ConsentFlow Overview](/core-concepts/consent-and-permissions/consentflow-overview)
* **Connecting an AI agent** → start with [Connect AI Agent](/how-to-guides/connect-systems/connect-ai-agent)
* **Integrating into a school or state** → start with [Use Cases & Possibilities](/introduction/use-cases-and-possibilities)

Or jump straight into [Your First Integration](/quick-start/your-first-integration).


# Interoperability

Explanation: Interoperability, Open Standards, and LearnCard

Our core philosophy is simple: **A user's identity and achievements belong to them, not to a platform.** This principle is not just a talking point; it is the architectural foundation of everything we build, and it is made possible through a deep commitment to interoperability and open standards.

## The Universal Passport: Beyond a Simple Wallet

It's helpful to think of LearnCard not as a closed "wallet," but as a **universal passport for learning and work.**

A physical passport is valuable because it's based on a global standard. It's issued by a trusted authority, but it's recognized and accepted by countless other countries, airlines, and institutions. You can take it anywhere, and its value is understood.

This is our model for digital identity. A credential for a course completed on one platform should be just as valid and useful on another. A skill verified by one employer should be presentable to the next. LearnCard is designed to be that trusted, portable container for these records—a passport that unlocks opportunities across the entire digital world, not just within our own walls.

## The Universal Inbox: The Bridge to Simplicity

The biggest challenge to interoperability isn't a lack of standards; it's the friction of adoption. How do you connect the simple, centralized systems everyone uses today (like email) with the powerful, decentralized world of verifiable identity?

This is the problem the [**Universal Inbox**](/core-concepts/network-and-interactions/universal-inbox) solves.

It acts as a simple, familiar on-ramp. By allowing an organization to send a credential to a simple email address or phone number, we remove the "chicken-and-egg" problem of decentralized identity. The developer doesn't need to ask, "Does this user have a passport yet?" They just send the record.

The Universal Inbox is the bridge that makes interoperability practical. It uses a simple, centralized interaction to bootstrap the user into a world where they are in full control of their own decentralized, interoperable records.

## A Foundation of Open Standards

Our commitment to interoperability is not theoretical; it's built into every layer of our platform through the adoption of global, open standards. This ensures that data created or managed within LearnCard is portable, secure, and understandable by any other compliant system.

* [**Verifiable Credentials (VCs)**](https://w3c.github.io/vc-data-model/)**:** The W3C standard for creating secure, tamper-evident digital records that are portable and independently verifiable.
* [**Decentralized Identifiers (DIDs)**](https://w3c.github.io/did/)**:** The W3C standard for creating globally unique, user-controlled identifiers that are not dependent on any single organization.
* [**VC-API**](https://w3c-ccg.github.io/vc-api)**:** A standard protocol that defines how wallets and servers communicate to issue and verify credentials, ensuring any compliant wallet can interact with any compliant issuer.
* [**OpenID for Verifiable Credential Issuance (OID4VCI)**](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html)**:** The OpenID Foundation standard for receiving credentials from an issuer. LearnCard can accept a credential offer (by scanning a QR code or following a link), complete the issuer's authorization, and store the resulting credentials—bridging the OpenID ecosystem with your LearnCard Passport.
* [**OpenID for Verifiable Presentations (OID4VP)**](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html)**:** The OpenID Foundation standard for presenting credentials to a verifier. LearnCard resolves a verifier's request, lets you choose which held credentials to share (with selective disclosure via DIF Presentation Exchange or DCQL), and returns a signed Verifiable Presentation.
* [**Self-Issued OpenID Provider v2 (SIOPv2)**](https://openid.net/specs/openid-connect-self-issued-v2-1_0.html)**:** The OpenID Foundation standard that lets your wallet act as its own identity provider, issuing self-signed ID tokens so you can authenticate to relying parties without a centralized login.
* [**Credential Handler API (CHAPI)**](http://chapi.io/): The Credential Handler API (CHAPI) allows your digital wallet to receive Verifiable Credentials from an independent third-party issuer - or present Verifiable Credentials to an independent third-party verifier - in a way that establishes trust and preserves privacy.
* [**Open Badges v3**](https://www.imsglobal.org/spec/ob/v3p0/)**:** The leading standard from 1EdTech for creating and recognizing digital badges for skills and achievements, which is fully compatible with the Verifiable Credentials model.
* [**Comprehensive Learner Record v2**](https://www.imsglobal.org/spec/clr/v2p0/)**:** The leading standard from 1EdTech designed to create, transmit, and render an individual's set of achievements, as issued by multiple learning providers, in a machine-readable format that can be curated into verifiable digital records of achievement.
* [**Credential Transparency Description Language (CTDL)**](https://credentialengine.org/credential-transparency/ctdl/)**:** A standard vocabulary for describing credentials, competencies, and educational pathways, making records machine-readable and easy to understand across different systems.

## Your Data, Your Passport: True Portability

Because we are built on these open standards, **you are never locked in.**

A user can, at any time, export their credentials and identifiers from their LearnCard Passport and import them into any other standards-compliant wallet. This is the ultimate guarantee of user control and freedom. It's not our platform that gives the credentials their value; it's the standards they are built upon. Our job is simply to provide the most secure, user-friendly passport for carrying them.

## The Wall of Interoperability

LearnCard is a node in a growing global network. Our ability to interoperate is proven by our compatibility with other tools and platforms that also embrace open standards.

**Compatible Wallets & Platforms:**

* [MIT's Learner Credential Wallet](https://lcw.app/)
* [iDatify's SmartResume](https://www.smartresume.com/)
* [IEEE's Open Credential Publisher](https://opencredentialpublisher.org/access/login)
* [Digital Bazaar's Veres One Wallet](https://veres.one/)
* [Spruce's Credible Wallet](https://spruceid.com/products/credible)
* [Acreditta](https://info.acreditta.com/)
* [ASU's Pocket](https://pocket.asu.edu/)
* [Danube Tech](https://danubetech.com/tech/uni-issuer-verifier)
* [Participate](https://www.participate.com/)
* [Gobekli](https://gobekli.io/)
* [VC Playground](https://vcplayground.org/)
* *And many others that implement the VC-API and OpenID4VC (OID4VCI / OID4VP) standards.*

**Supported Standards Bodies:**

* [W3C Credentials Community Group](https://www.w3.org/community/credentials/)
* [1EdTech](https://www.1edtech.org/) (formerly IMS Global)
* [IEEE](https://www.ieee.org/)
* [Decentralized Identity Foundation (DIF)](https://identity.foundation/)
* [OpenID Foundation](https://openid.net/)

This ecosystem is a testament to the power of collaborative, open development. Our goal is to make LearnCard the easiest way to participate in it.


# Setup & Prerequisites

Welcome! This guide covers the essential setup required to start building with **LearnCard**. Following these steps will get your environment ready for our Quick Start and tutorials.

***

### 1. 🧭 Introduction

This guide will help you:

* Set up your development environment
* Install core SDKs and tools
* Verify everything is working before your first credential issuance

> **Assumed Knowledge:**\
> You should be familiar with:
>
> * Basic terminal/command line use
> * JavaScript or TypeScript
> * Package managers like npm, yarn, or Bun

All examples use modern JavaScript and run on Node.js.

***

### 2. 🖥️ System Requirements

#### ✅ Supported OS

* **macOS** (M1/M2/M3+ supported)
* **Windows** (Use [**WSL**](https://learn.microsoft.com/en-us/windows/wsl/install) for best results)
* **Linux** (Ubuntu LTS recommended)

#### 🔧 Node.js + npm/yarn

* Required version: **Node.js v18.x or v20.x**
* Node.js includes npm by default.

📥 [Download Node.js](https://nodejs.org/)

**🔎 Verify:**

```bash
node -v
npm -v
# or if using yarn
yarn -v
```

#### 🔧 Git

Git is required for cloning repositories and managing code.

📥 [Download Git](https://git-scm.com/downloads)

**🔎 Verify:**

```bash
git --version
```

***

### 3. 👤 Account Setup (Optional)

#### Developer Account

You **do not need** a developer account to complete the Quick Start.

You'll be using local/demo network capabilities for your first integration. For live network use later, you can create a developer profile.

#### API Keys

Not required for Quick Start.

> You’ll need them later for:
>
> * Live network integrations
> * Custom signing authorities
> * Auth-granted service access

***

### 4. 📦 Install the Wallet SDK

To get started with the [LearnCard Wallet SDK](/sdks/learncard-core):

```bash
# Using npm
npm install @learncard/init

# Using yarn
yarn add @learncard/init

# Using Bun
bun add @learncard/init
```

This installs the essential module to initialize LearnCard

***

### 5. 🔧 Install Essential Tools (Optional for Quick Start)

#### LearnCard CLI (Optional)

If you want to interactively explore wallet actions via terminal:

```bash
npx @learncard/cli
```

This launches an interactive REPL where you can issue credentials, verify them, and explore the SDK.

→ See [CLI Documentation](/sdks/learncard-cli)

#### Seed Phrases

You **don’t need** an external wallet for Quick Start. LearnCard uses secure, deterministic wallets derived from seed phrases. You’ll use an environment variable (`SECURE_SEED`) for your first script.

***

### 6. 💡 Recommended Tools

#### Code Editor

We recommend [Visual Studio Code](https://code.visualstudio.com/)

#### Helpful Extensions:

* **ESLint** – Code quality
* **Prettier** – Auto-formatting
* **dotenv** – Recognizes environment variables in `.env` files

***

### 7. ✅ Check Your Setup

Run the following commands to confirm everything is ready:

```bash
# Verify Node + package manager
node -v
npm -v
# or yarn -v / bun -v

# Verify Git
git --version

# (Optional) Launch CLI
npx @learncard/cli
```

Need help? Visit our [Troubleshooting Guide ](/sdks/learncard-core/troubleshooting-guide)or join the Community for support.

***

### 8. 🚀 Next Steps

You’re ready to go!

👉 **Proceed to the** [**Quick Start: Issue Your First Digital Badge →**](/quick-start/your-first-integration)


# Your First Integration

Claim Your First Digital Badge in 5 Minutes!

Welcome to your first LearnCard integration! In just a few lines of code, you'll create a verifiable, claimable digital badge—what we call a **Boost**.

This quickstart helps you:

* Install LearnCard tools
* Create a demo issuer profile
* Generate a verifiable Boost (credential)
* Output a link that anyone can claim

No experience required. Just code, coffee, and a terminal.

## ⭐️ What You'll Be Making

{% embed url="<https://codepen.io/Jacks-n-Smith/pen/KwwEbjY>" fullWidth="false" %}

## 🧰 Installation

Choose your preferred package manager:

```bash
# Using npm
npm install @learncard/init @learncard/claimable-boosts-plugin @learncard/lca-api-plugin dotenv

# Using yarn
yarn add @learncard/init @learncard/claimable-boosts-plugin @learncard/lca-api-plugin dotenv

# Using Bun
bun add @learncard/init @learncard/claimable-boosts-plugin @learncard/lca-api-plugin dotenv

```

## 🚀 Quickstart Script

This script:

1. Initializes a LearnCard wallet
2. Creates an issuer profile
3. Defines a Boost template
4. Issues the Boost to the network
5. Generates a claim link for anyone to redeem

## ✅ Prerequisites

* Node.js (v18+)
* A secure seed phrase (stored in `SECURE_SEED`)
* A unique ID for your issuer (e.g. `my-awesome-org-profile`)

## 📁 Create `createBoost.js`:

<pre class="language-javascript"><code class="lang-javascript">import 'dotenv/config';

<strong>import { initLearnCard } from '@learncard/init';
</strong>import { getClaimableBoostsPlugin } from '@learncard/claimable-boosts-plugin';
import { getLCAPlugin } from '@learncard/lca-api-plugin';

const DEMO_SEED = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdee'
const secure_seed = process.env.SECURE_SEED;
const profileId = process.env.PROFILE_ID || 'my-awesome-org-profile';
const profileName = process.env.PROFILE_NAME || 'My Awesome Org';

// Use user provided seed, or use backup DEMO seed 
const seed = secure_seed || DEMO_SEED;
if (!secure_seed) {
  console.warn('Warning: SECURE_SEED environment variable is not set, using DEMO_SEED.');
}

async function quickstartBoost() {
  try {
    console.log('Initializing LearnCard...');
    const learnCard = await initLearnCard({
      seed: seed,
      network: true,
      allowRemoteContexts: true
    });

    const lcaApiLearnCard = await learnCard.addPlugin(
      await getLCAPlugin(learnCard, 'https://api.learncard.app/trpc')
    );

    const claimableLearnCard = await lcaApiLearnCard.addPlugin(
      await getClaimableBoostsPlugin(lcaApiLearnCard)
    );
    console.log('LearnCard initialized with plugins.');

    try {
      console.log(`Creating profile "${profileId}"...`);
      await claimableLearnCard.invoke.createProfile({
        profileId: profileId,
        displayName: profileName,
        description: 'Issuing awesome credentials.',
      });
      console.log(`Profile "${profileId}" created successfully.`);
    } catch (error) {
      if (error.message?.includes('Profile already exists')) {
        console.log(`Profile "${profileId}" already exists, continuing.`);
      } else {
        throw new Error(`Failed to create profile: ${error.message}`);
      }
    }

    console.log('Creating boost template...');
    const boostTemplate = claimableLearnCard.invoke.newCredential({
      type: 'boost', 
      boostName: 'Quickstart Achievement',
      boostImage: 'https://placehold.co/400x400?text=Quickstart',
      achievementType: 'Influencer',
      achievementName:'Quickstart Achievement',
      achievementDescription: 'Completed the quickstart guide!',
      achievementNarrative: 'User successfully ran the quickstart script.',
      achievementImage: 'https://placehold.co/400x400?text=Quickstart'
    });
    console.log('Boost template created.');

    console.log('Creating boost on the network...');
    const boostUri = await claimableLearnCard.invoke.createBoost(
      boostTemplate,
      {
        name: boostTemplate.name,
        description: boostTemplate.achievementDescription,
      }
    );
    console.log(`Boost created with URI: ${boostUri}`);

    console.log('Generating claim link...');
    const claimLink = await claimableLearnCard.invoke.generateBoostClaimLink(boostUri);
    console.log('\n✅ Success! Your Claimable Boost link is ready:');
    console.log(claimLink);

    return claimLink;

  } catch (error) {
    console.error('\n❌ Error during quickstart process:', error);
    process.exit(1);
  }
}

quickstartBoost();

</code></pre>

## 🔩 Setup Organization Config (optional)

{% hint style="danger" %}
This step sets up your secret seed phrase for controlling your Organization's profile. However, for demonstration, **you may safely skip this step to use the provided DEMO\_SEED**. Never hardcode a seed in production. Learn more about [seeds](/core-concepts/identities-and-keys/seed-phrases).
{% endhint %}

#### Create and save your seed to .en&#x76;*:*

{% tabs %}
{% tab title="macOS / Linux" %}
Run the following command in your terminal:

{% code overflow="wrap" %}

```bash
echo "SECURE_SEED=\"$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")\"" > .env
```

{% endcode %}
{% endtab %}

{% tab title="Windows Cmd" %}
Run the following command in your Windows cmd prompt:

{% code overflow="wrap" %}

```bash
echo "SECURE_SEED=\"$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")\"" > .env
```

{% endcode %}
{% endtab %}

{% tab title="Powershell" %}
Run the following command in Powershell:

{% code overflow="wrap" %}

```bash
"SECURE_SEED=\"$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")\"" | Out-File -Encoding utf8 .env
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Add config variables to your `.env`:

{% hint style="info" %}
You must create a unique profile ID for your organization. It must be 3-40 characters, lowercase, no spaces or special characters. E.g.: `my-organization`, `acme`, `taffy-co-organization` , etc.
{% endhint %}

{% code title=".env" overflow="wrap" %}

```bash
SECURE_SEED="..." # Created from command in prior step.
PROFILE_ID="<unique-profile-id>" # Unique profile ID.
PROFILE_NAME="<Display Name>" # Human Readable Display Name
```

{% endcode %}

## 🏃‍♂️ Run the Script

```bash
node createBoost.js
```

## 🎉 What You'll See

The console will print a claimable URL like:

```arduino
✅ Success! Your Claimable Boost link is ready:
https://claim.learncard.app/boost/abc123...
```

Anyone with that link can scan or click to claim their badge. It’s a live verifiable credential issued by your script.

{% hint style="success" %}
Want to customize your claimable boost even more? Check out our Core Concepts guide on "[Getting Started with Boosts](/core-concepts/credentials-and-data/getting-started-with-boosts)."
{% endhint %}

## ➡️ Next Steps

* 📝 Play with sending different kinds of credentials (see[ Building Verifiable Credentials](/core-concepts/credentials-and-data/building-verifiable-credentials))
* 🔐 Add expiration, limits, or QR codes (see [Detailed Usage](https://github.com/learningeconomy/LearnCard/tree/main/docs/sdks/official-plugins/claimable-boosts.md))
* 🧠 Learn how Boosts work under the hood (see [Core Concepts](/core-concepts/credentials-and-data/boost-credentials))
* 🛠️ [Issue credentials ](/tutorials/create-a-credential)dynamically in your app or game

**You just built your first digital credential.**\
You’ve touched real-world decentralized identity and verifiable credentials—with just a few lines of code.

We’re glad you’re here. Ready to build something great?


# Send Credentials

How-To Guide: Sending Credentials with LearnCard

This guide provides practical, step-by-step recipes for sending credentials. We'll start with the simplest possible use case and progressively add more powerful configurations.

***

## Quick Start: The `send` Method (Recommended)

The `send` method is the simplest and most ergonomic way to send credentials to recipients. It handles credential issuance, signing, and delivery in a single call.

**The `send` method automatically detects your recipient type:**

* **Profile ID** → Direct delivery to their LearnCard
* **DID** → Direct delivery via DID resolution
* **Email** → Routes through Universal Inbox (sends claim email)
* **Phone** → Routes through Universal Inbox (sends claim SMS)

### Prerequisites

* LearnCard SDK initialized with `network: true`
* A [signing authority](/how-to-guides/create-signing-authority) configured (for server-side signing) **OR** local key material available (for client-side signing) **OR** a pre-signed credential (no signing authority needed)

### Basic Usage

{% tabs %}
{% tab title="Send to Profile ID or DID" %}

```typescript
// Send to an existing LearnCard user
const result = await learnCard.invoke.send({
    type: 'boost',
    recipient: 'recipient-profile-id', // or 'did:key:z6Mk...'
    templateUri: 'urn:lc:boost:abc123',
});

console.log(result.credentialUri); // URI of the sent credential
console.log(result.uri);           // URI of the boost template used
```

{% endtab %}

{% tab title="Send to Email" %}

```typescript
// Send to someone via email (they'll get a claim link)
const result = await learnCard.invoke.send({
    type: 'boost',
    recipient: 'student@example.com', // Auto-detected as email
    templateUri: 'urn:lc:boost:abc123',
    options: {
        branding: {
            issuerName: 'My Organization',
            issuerLogoUrl: 'https://example.com/logo.png',
            recipientName: 'John Doe',
        },
        webhookUrl: 'https://api.example.com/webhooks/claimed',
    },
});

console.log(result.inbox?.claimUrl);   // Claim URL (if suppressDelivery=true)
console.log(result.inbox?.issuanceId); // Issuance tracking ID
```

{% endtab %}

{% tab title="Send to Phone" %}

```typescript
// Send to someone via SMS
const result = await learnCard.invoke.send({
    type: 'boost',
    recipient: '+15551234567', // Auto-detected as phone
    templateUri: 'urn:lc:boost:abc123',
    options: {
        suppressDelivery: true, // Don't send SMS, just get claimUrl
    },
});

// Use result.inbox.claimUrl in your own notification
```

{% endtab %}

{% tab title="Creating a New Boost On-the-Fly" %}

```typescript
// Send by creating a new boost from an unsigned credential
const result = await learnCard.invoke.send({
    type: 'boost',
    recipient: 'recipient-profile-id',
    template: {
        credential: {
            "@context": [
                "https://www.w3.org/2018/credentials/v1",
                "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json"
            ],
            "type": ["VerifiableCredential", "OpenBadgeCredential"],
            "issuer": "did:web:example.com",
            "name": "Course Completion",
            "credentialSubject": {
                "type": ["AchievementSubject"],
                "achievement": {
                    "type": ["Achievement"],
                    "name": "Web Development 101",
                    "description": "Completed the Web Development fundamentals course.",
                    "criteria": {
                        "narrative": "Successfully completed all modules and passed the final assessment."
                    }
                }
            }
        },
        name: 'Web Development 101 Certificate',
        category: 'Achievement',
    },
});
```

{% endtab %}

{% tab title="Send a Pre-Signed Credential" %}

```typescript
// Sign a credential yourself, then send it — no template needed
const signedCredential = await learnCard.invoke.issueCredential({
    "@context": [
        "https://www.w3.org/ns/credentials/v2",
        "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json"
    ],
    "type": ["VerifiableCredential", "OpenBadgeCredential"],
    "issuer": learnCard.id.did(),
    "credentialSubject": {
        "type": ["AchievementSubject"],
        "achievement": {
            "type": ["Achievement"],
            "name": "Teamwork Badge",
            "description": "Recognized for outstanding collaboration.",
            "criteria": { "narrative": "Nominated by peers." }
        }
    }
});

const result = await learnCard.invoke.send({
    type: 'boost',
    recipient: 'recipient@example.com', // or profile ID, DID
    signedCredential,
});
```

{% endtab %}

{% tab title="With ConsentFlow Contract" %}

```typescript
// Send through a consent flow contract
// Automatically routes via consent terms if the recipient has consented
const result = await learnCard.invoke.send({
    type: 'boost',
    recipient: 'recipient-profile-id',
    templateUri: 'urn:lc:boost:abc123',
    contractUri: 'urn:lc:contract:xyz789', // Optional: link to consent contract
});
```

{% endtab %}
{% endtabs %}

### REST API (`POST /send`)

The `send` method is also available as a REST endpoint. Use an API key or bearer token for authentication.

{% tabs %}
{% tab title="cURL: Send with Template" %}

```bash
curl -X POST https://network.learncard.com/api/send \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "boost",
    "recipient": "student@example.com",
    "templateUri": "urn:lc:boost:abc123"
  }'
```

{% endtab %}

{% tab title="cURL: Send Pre-Signed Credential" %}

```bash
curl -X POST https://network.learncard.com/api/send \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "boost",
    "recipient": "student@example.com",
    "signedCredential": {
      "@context": [
        "https://www.w3.org/ns/credentials/v2",
        "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json",
        "https://w3id.org/security/suites/ed25519-2020/v1"
      ],
      "type": ["VerifiableCredential", "OpenBadgeCredential"],
      "issuer": { "id": "did:web:example.com" },
      "validFrom": "2025-01-01T00:00:00Z",
      "name": "Teamwork Badge",
      "credentialSubject": {
        "type": ["AchievementSubject"],
        "achievement": {
          "type": ["Achievement"],
          "name": "Teamwork",
          "description": "Recognized for outstanding collaboration.",
          "criteria": { "narrative": "Nominated by peers." }
        }
      },
      "proof": {
        "type": "Ed25519Signature2020",
        "proofPurpose": "assertionMethod",
        "proofValue": "z...",
        "verificationMethod": "did:web:example.com#owner",
        "created": "2025-01-01T00:00:00Z"
      }
    }
  }'
```

{% endtab %}

{% tab title="JavaScript (fetch)" %}

```javascript
const response = await fetch('https://network.learncard.com/api/send', {
    method: 'POST',
    headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({
        type: 'boost',
        recipient: 'student@example.com',
        signedCredential: mySignedVC, // A previously signed VC object
    }),
});

const result = await response.json();
console.log(result);
// { type: 'boost', uri: 'urn:lc:boost:...', inbox: { issuanceId: '...', status: 'PENDING' } }
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
**All SDK parameters work in the REST API too** — `templateUri`, `template`, `signedCredential`, `templateData`, `options`, and `contractUri` are all supported in the JSON body.
{% endhint %}

### How It Works

1. **Detects recipient type** - Automatically determines if recipient is email, phone, DID, or profile ID
2. **Routes appropriately** - Uses direct send for profiles/DIDs, Universal Inbox for email/phone
3. **Prepares the credential** - Uses your template, creates a new template on-the-fly, or uses your pre-signed credential as-is
4. **Signs the credential** - Skips signing if you provided a `signedCredential`; otherwise uses client-side signing if available, or falls back to your registered signing authority
5. **Delivers the credential** - Direct delivery or sends claim email/SMS based on recipient type
6. **Auto-delivery for verified users** - If the email/phone is already verified and linked to a LearnCard profile, the credential is delivered directly to their wallet without requiring them to click a claim link

{% hint style="info" %}
**Pre-Signed Credentials**: When you provide only `signedCredential` (without `templateUri` or `template`), the system automatically creates a template from your credential. This is ideal when you've already signed the credential yourself and don't need the server to sign it. Your original proof is preserved through the entire flow, including email inbox claims.
{% endhint %}

### Guardian-Gated Credentials

To require guardian (parent) approval before a minor can claim a credential, add `guardianEmail` to `options`:

```typescript
const result = await learnCard.invoke.send({
    type: 'boost',
    recipient: 'student@school.edu',
    templateUri: 'urn:lc:boost:abc123',
    options: {
        guardianEmail: 'parent@example.com',
    },
});

console.log(result.inbox?.guardianStatus); // 'AWAITING_GUARDIAN'
```

The guardian receives an approval email with an OTP challenge. The student cannot claim the credential until the guardian approves. See [Guardian-Gated Credentials](/how-to-guides/implement-flows/guardian-gated-credentials) for the full guide.

### Response

```typescript
interface SendResponse {
    type: 'boost';
    credentialUri: string; // URI of the issued credential
    uri: string;           // URI of the boost template
    
    // Only present when sent to email/phone recipients
    inbox?: {
        issuanceId: string;  // Tracking ID for this issuance
        status: 
            | 'PENDING'      // Waiting to be claimed
            | 'ISSUED'       // Auto-delivered to verified user
            | 'CLAIMED';     // Claimed via claim link
        claimUrl?: string;   // Present when suppressDelivery=true
        recipientDid?: string; // DID of recipient (present when ISSUED)
        guardianStatus?:       // Present when guardianEmail was specified
            | 'AWAITING_GUARDIAN'  // Waiting for guardian approval
            | 'GUARDIAN_APPROVED'  // Guardian approved
            | 'GUARDIAN_REJECTED'; // Guardian rejected
    };
}
```

{% hint style="success" %}
**Auto-Delivery**: When `status` is `ISSUED`, the credential was automatically delivered to the recipient's wallet because their email/phone was already verified. No claim link was needed!
{% endhint %}

### Options (for Email/Phone Recipients)

When sending to email or phone recipients, you can provide additional options:

```typescript
options: {
    webhookUrl?: string;       // URL to receive claim notifications
    suppressDelivery?: boolean; // If true, returns claimUrl without sending email/SMS
    branding?: {
        issuerName?: string;    // Your organization name
        issuerLogoUrl?: string; // Your logo URL
        credentialName?: string; // Display name for the credential
        recipientName?: string;  // Recipient's name for personalization
    };
}
```

{% hint style="info" %}
**Contract Integration**: When you provide a `contractUri`, the method automatically:

* Checks if the recipient has consented to the contract
* Routes the credential through the consent flow if terms exist
* Creates a `RELATED_TO` relationship between new boosts and the contract
  {% endhint %}

{% hint style="info" %}
**Email Verification**: When a recipient claims a credential via an email claim link, their email address becomes a **verified contact method** linked to their LearnCard profile. This means:

* Future credentials sent to that email will be **auto-delivered** directly to their wallet
* No claim link is needed for subsequent issuances
* The issuer receives `status: 'ISSUED'` instead of `status: 'PENDING'`
  {% endhint %}

***

## Tracking Boost Recipients

You can track which users have received credentials from a specific boost template using `getBoostRecipients`:

```typescript
// Get all recipients of a boost
const recipients = await learnCard.invoke.getBoostRecipients(boostUri);

console.log(recipients);
// [
//   { to: { profileId: 'alice-123', did: 'did:key:z6Mk...' }, sent: '2025-01-09T...' },
//   { to: { profileId: 'bob-456', did: 'did:key:z6Mk...' }, sent: '2025-01-08T...' },
// ]
```

This is useful for:

* **Auditing**: See who has received a specific credential
* **Preventing duplicates**: Check if a user already received a boost before sending
* **Analytics**: Track issuance metrics for your credentials

***

## Dynamic Templates with `templateData`

Use Mustache-style templates to personalize credentials with unique data for each recipient. This is perfect for issuing the same type of credential (like course completions) with recipient-specific details.

### Creating a Templated Boost

First, create a boost with `{{variableName}}` placeholders:

```typescript
const templatedBoostUri = await learnCard.invoke.createBoost({
    "@context": [
        "https://www.w3.org/2018/credentials/v1",
        "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json"
    ],
    "type": ["VerifiableCredential", "OpenBadgeCredential"],
    "issuer": learnCard.id.did(),
    "issuanceDate": new Date().toISOString(),
    "name": "Certificate for {{courseName}}",
    "credentialSubject": {
        "type": ["AchievementSubject"],
        "achievement": {
            "type": ["Achievement"],
            "name": "{{courseName}} - {{level}}",
            "description": "Awarded to {{studentName}} for completing {{courseName}} with grade {{grade}}",
            "criteria": { "narrative": "Complete all course modules" }
        }
    }
}, { name: 'Course Completion Template' });
```

### Sending with Personalized Data

Provide `templateData` when sending to substitute the variables:

```typescript
const result = await learnCard.invoke.send({
    type: 'boost',
    recipient: 'recipient-profile-id',
    templateUri: templatedBoostUri,
    templateData: {
        courseName: 'Web Development 101',
        level: 'Beginner',
        studentName: 'Alice Smith',
        grade: 'A',
    },
});
```

The issued credential will have all placeholders replaced with the provided values.

### Batch Issuance to Multiple Recipients

```typescript
const students = [
    { profileId: 'alice', name: 'Alice Smith', grade: 'A' },
    { profileId: 'bob', name: 'Bob Johnson', grade: 'B+' },
    { profileId: 'charlie', name: 'Charlie Brown', grade: 'A-' },
];

for (const student of students) {
    await learnCard.invoke.send({
        type: 'boost',
        recipient: student.profileId,
        templateUri: templatedBoostUri,
        templateData: {
            courseName: 'Web Development 101',
            level: 'Beginner',
            studentName: student.name,
            grade: student.grade,
        },
    });
}
```

### Special Characters

Template values are automatically escaped for JSON safety. You can safely include:

* Quotes: `"Course with \"quotes\""`
* Newlines: `"Line 1\nLine 2"`
* Backslashes: `"Path\\to\\file"`
* Unicode: `"Café ☕ 日本語"`

{% hint style="info" %}
**Missing Variables**: If a variable in the template isn't provided in `templateData`, it renders as an empty string. This allows for optional fields.
{% endhint %}

For more details, see [Dynamic Templates with Mustache](/core-concepts/credentials-and-data/boost-credentials#dynamic-templates-with-mustache).

***

## Alternative: Universal Inbox API

For advanced use cases requiring full control over the inbox issuance process, you can use the `sendCredentialViaInbox` method directly. This is useful when you need:

* Full configuration control (signing authority, expiration, etc.)
* To send raw credentials (not boost templates)
* Custom template IDs for email/SMS

This approach assumes you are familiar with the core concepts of the [Universal Inbox](/core-concepts/network-and-interactions/universal-inbox) and have [a valid API token](https://docs.learncard.com/how-to-guides/pages/eP0SSkHGKKyBLHnAiFvt#id-2.-using-a-scoped-api-token) & [signing authority](/how-to-guides/create-signing-authority) set up.

## 1. The Simplest Case: Fire and Forget

Your goal is to send a single, verifiable record to a user. You want our system to handle all the complexity of signing the credential and notifying the user.

This is the most common use case, perfect for one-off issuances like a course completion certificate.

**The Recipe:** Make a `POST` request to the `/inbox/issue` endpoint with only two required fields: `recipient` and a *signed* or *unsigned* `credential`. An unsigned credential requires [a configured signing authority](/how-to-guides/create-signing-authority).

**Example:**

{% tabs %}
{% tab title="SDK" %}

```javascript
// A bootcamp sending an "Advanced Javascript" achievement to a student.
await learnCard.invoke.sendCredentialViaInbox({ 
  recipient: { 
    type: 'email', 
    value: 'student@school.edu' 
  }, 
  credential: {
    "@context": [
        "https://www.w3.org/2018/credentials/v1",
        "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json"
    ],
    "id": "http://example.com/credentials/3527",
    "type": [
        "VerifiableCredential",
        "OpenBadgeCredential"
    ],
    "issuer": "did:key:z6Mku381DztEvDosbgR5RZrvLxMhVgJ33sLVhTnngDuUA5bM",
    "issuanceDate": "2025-07-03T17:54:56.881Z",
    "name": "Advanced Javascript",
    "credentialSubject": {
        "id": "did:example:d23dd687a7dc6787646f2eb98d0",
        "type": [
            "AchievementSubject"
        ],
        "achievement": {
            "id": "https://example.com/certificates/javascript/advanced",
            "type": [
                "Achievement"
            ],
            "criteria": {
                "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management."
            },
            "description": "This badge recognizes advanced javasript proficiency.",
            "name": "Advanced Javascript"
        }
    }
  }
})

// Retrieve sent inbox credential
const sentInbox = await learnCard.invoke.getMySentInboxCredentials()
const inboxCredId = sentInbox.records[0].id

// Retrieve inbox credential
await learnCard.invoke.getInboxCredential(inboxCredId)
```

{% endtab %}

{% tab title="Javascript" %}

```javascript
// A bootcamp sending an "Advanced Javascript" achievement to a student.
const apiKey = 'YOUR_API_KEY';

const response = await fetch('https://network.learncard.com/api/inbox/issue', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    recipient: {
      type: 'email',
      value: 'student@example.com',
    },
    credential: {
      "@context": [
        "https://www.w3.org/2018/credentials/v1",
        "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json"
      ],
      "id": "http://example.com/credentials/3527",
      "type": [
        "VerifiableCredential",
        "OpenBadgeCredential"
      ],
      "issuer": "did:key:z6Mku381DztEvDosbgR5RZrvLxMhVgJ33sLVhTnngDuUA5bM",
      "issuanceDate": "2025-07-03T17:54:56.881Z",
      "name": "Advanced Javascript",
      "credentialSubject": {
        "id": "did:example:d23dd687a7dc6787646f2eb98d0",
        "type": [
          "AchievementSubject"
        ],
        "achievement": {
          "id": "https://example.com/certificates/javascript/advanced",
          "type": [
            "Achievement"
          ],
          "criteria": {
            "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management."
          },
          "description": "This badge recognizes advanced javasript proficiency.",
          "name": "Advanced Javascript"
        }
      }
    },
  }),
});

const data = await response.json();
console.log(data);
```

{% endtab %}

{% tab title="cURL" %}

```bash
curl -X POST https://network.learncard.com/api/inbox/issue \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "recipient": {
      "type": "email",
      "value": "student@example.com"
    },
    "credential": {
      "@context": [
        "https://www.w3.org/2018/credentials/v1",
        "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json"
      ],
      "id": "http://example.com/credentials/3527",
      "type": [
        "VerifiableCredential",
        "OpenBadgeCredential"
      ],
      "issuer": "did:key:z6Mku381DztEvDosbgR5RZrvLxMhVgJ33sLVhTnngDuUA5bM",
      "issuanceDate": "2025-07-03T17:54:56.881Z",
      "name": "Advanced Javascript",
      "credentialSubject": {
        "id": "did:example:d23dd687a7dc6787646f2eb98d0",
        "type": [
          "AchievementSubject"
        ],
        "achievement": {
          "id": "https://example.com/certificates/javascript/advanced",
          "type": [
            "Achievement"
          ],
          "criteria": {
            "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management."
          },
          "description": "This badge recognizes advanced javasript proficiency.",
          "name": "Advanced Javascript"
        }
      }
    }
  }'
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}

## Have you configured your default Primary Signing Authority?

If you get an error about a missing signing authority, **ensure you've set one up** [**following this guide**](/how-to-guides/create-signing-authority)**.** When you send an unsigned credential with Universal Inbox, it will use your primary signing authority to sign the credential when a user claims it.

If you'd like to use a custom signing authority, or specify it per request:

```javascript
// Note the explicit `signingAuthority` object in the configuration.
await learncardApiClient.post('/inbox/issue', {
  recipient: { /* ... */ },
  credential: { /* ...unsigned credential data... */ },
  configuration: {
    signingAuthority: {
      name: 'my-custom-signer',
      endpoint: 'https://my-vc-api.my-org.com/issue'
    }
  }
});
```

{% endhint %}

**What Happens:**

* Our system receives the unsigned credential data.
* It sends a professionally designed email to `student@example.com` with a secure link to claim their record.
* When the student claims their record, it automatically signs it using your default Primary Signing Authority attached to your profile.

You're done. The rest of the user onboarding and claim process is handled for you.

## 2. Customizing the User Experience

Your goal is to send a credential, but you want the notification email to be branded with your organization's identity to build trust and recognition.

**The Recipe:** Use the optional `configuration.delivery.template.model` object to provide your branding details.

**Example:**

```javascript
// A university sending a branded digital transcript.

await learncardApiClient.post('/inbox/issue', {
  recipient: {
    type: 'email',
    value: 'student@stateu.edu',
  },
  credential: { /* ... */ },
  configuration: {
    delivery: {
      template: {
        model: {
          issuer: {
            name: 'State University',
            logoUrl: 'https://stateu.edu/logo.png', //1024px x 1024px Recommended
          },
          credential: {
            name: 'Official Fall Semester Transcript',
            type: 'transcript',
          },
          recipient: {
            name: 'John Doe'
          }
        },
      },
    },
  },
});

```

**What Happens:** The email sent to the student will now feature the State University name and logo prominently, creating a more professional and trustworthy experience.

## 3. Taking Control of Delivery and Status

You have more advanced needs. You might want to deliver the claim link through your own system (e.g., inside your web portal) or need to know precisely when a user has successfully claimed their record.

### **Recipe 3a: Suppressing Delivery**

**Goal:** You want to get a `claimUrl` from our API but prevent us from sending any emails or texts.

**The Recipe:** Set `configuration.delivery.suppress` to `true`.

**Example:**

```javascript
// An HR platform embedding a claim link directly in their onboarding portal.

const response = await learncardApiClient.post('/inbox/issue', {
  recipient: { /* ... */ },
  credential: { /* ... */ },
  configuration: {
    delivery: {
      suppress: true,
    },
  },
});

// Use the claimUrl from the response to create a button in your own UI.
const claimUrl = response.data.claimUrl;

```

### **Recipe 3b: Tracking Status with Webhooks**

**Goal:** You need your system to be notified when a user successfully claims their credential so you can update your internal database.

**The Recipe:** Provide a `configuration.webhookUrl`.

**Example:**

```javascript
// A professional association tracking when a member claims their certificate.

await learncardApiClient.post('/inbox/issue', {
  recipient: { /* ... */ },
  credential: { /* ... */ },
  configuration: {
    webhookUrl: 'https://api.myassociation.org/learncard/hooks',
  },
});

```

**What Happens:** When the user claims their record, our system will send a `POST` request to your webhook URL with a payload containing the `issuanceId`, a `status` of `CLAIMED`, and the user's permanent `recipientDid`.

## 4. Advanced: Building an Ongoing Relationship

{% hint style="warning" %}
**This feature is currently in beta.** *Please reach out the the LearnCard team if you'd like early access!*
{% endhint %}

**Goal:** You plan to send credentials to the same user repeatedly over time (e.g., skill badges, course completions). You want to ask for their permission once, so future records can be sent directly to their passport without them needing to claim each one individually.

**The Recipe:** On the *first* issuance, include the `consentRequest` object.

**Example:**

```javascript
// A corporate learning platform that will issue multiple skill badges over time.

await learncardApiClient.post('/inbox/issue', {
  recipient: { type: 'email', value: 'employee@acme.com' },
  credential: { /* ... */ },
  
  // ONLY AVAILABLE IN BETA - WILL FAIL IN PRODUCTION
  consentRequest: {
    scopes: ['credential:write:Badge', 'credential:write:SkillAssertion'],
    description: 'Allow Acme Corp to automatically add new skill badges and certificates to your LearnCard Passport.',
  },
  configuration: {
    webhookUrl: 'https://api.acme.com/hooks/learncard',
  },
});

```

**What Happens:**

1. The employee claims their first badge as normal.
2. Immediately after, a prompt appears asking for their permission based on your `description`.
3. If they allow it, your webhook receives a notification that includes a `contractId`.
4. For all future issuances to this user, credentials will appear directly in their passport, friction-free.


# Export & Import Your Data

How-To Guide: Export, restore, and import all of your LearnCard data

This guide is a step-by-step recipe for taking **all of your LearnCard data** with you: your keys, credentials, presentations, consent records, and more. You can save it as a single encrypted file, restore your original account from it, or copy its contents into a brand-new account.

{% hint style="success" %}
**Our commitment to holder continuity**

Your data is yours. LearnCard is built so that you can always export everything that defines your identity and move it elsewhere — **without asking us for permission and without our servers needing to cooperate.** The export is a normal ZIP file built on open standards (W3C Verifiable Credentials, DIDs, JSON), so it stays useful for verification and migration even if LearnCard services are unavailable. Read the full commitment in [Holder Continuity](/core-concepts/holder-continuity).
{% endhint %}

***

## What's in the export

An export ("bundle") is a single password-protected ZIP file containing:

* Your **key material** (private-key seed, recovery phrase, and JWKs) — encrypted
* Your **DID** and DID document
* All of your **Verifiable Credentials and Presentations** — exactly as issued, with signatures preserved
* Your **wallet index** records (so credentials keep their titles and categories)
* Your **consent records** and transaction history
* **Status-list snapshots** for credential revocation state at export time
* A readable `manifest.json` listing every file and its SHA-256 hash

Everything sensitive is encrypted per-file using **Argon2id + AES-256-GCM**. The `manifest.json` itself stays readable so you can inspect the contents without the password.

{% hint style="warning" %}
**Treat your export like a password-vault backup.** It contains your full private key (encrypted). Anyone who has both the file **and** its password can take full control of your account, so store it offline, use a strong unique password, and rotate your account if the file is ever exposed.
{% endhint %}

### Prerequisites

* An initialized LearnCard wallet (`@learncard/init`)
* The `@learncard/holder-continuity` package installed:

```bash
bun add @learncard/holder-continuity
```

***

## 1. Export your data

This is the most common task: save everything to an encrypted file.

{% tabs %}
{% tab title="SDK" %}

```typescript
import { exportLearnCardBundle } from '@learncard/holder-continuity';

await exportLearnCardBundle(learnCard, {
    out: './learncard-export.zip',
    password: 'use-a-strong-password',
});
```

{% endtab %}

{% tab title="CLI" %}

```js
// The LearnCard CLI prompts for the password without echoing it,
// so it never lands in your shell history.
const password = await getLearnCardBundlePassword();

await exportLearnCardBundle(learnCard, {
    out: './learncard-export.zip',
    password,
});
```

{% hint style="info" %}
If you omit the wallet, the CLI exports the default `learnCard` wallet it created at startup: `await exportLearnCardBundle({ out: './learncard-export.zip', password })`.
{% endhint %}
{% endtab %}
{% endtabs %}

That's it — you now have a portable, encrypted copy of your account at `./learncard-export.zip`.

***

## 2. Inspect an export (optional)

You can read a bundle back to confirm what it contains. The decrypted entries are plain W3C JSON.

```typescript
import { readLearnCardBundle } from '@learncard/holder-continuity';

const bundle = await readLearnCardBundle('./learncard-export.zip', {
    password: 'use-a-strong-password',
});

console.log(bundle.manifest.primaryDid); // your DID
console.log(bundle.entries.length); // number of items exported
console.log(bundle.warnings); // anything skipped during export
```

***

## 3. Restore your original account

Use **restore** when you want your **original identity back** — same key, same DID. This decrypts the exported seed and rebuilds the wallet.

```typescript
import { restoreLearnCardFromBundle } from '@learncard/holder-continuity';

const restored = await restoreLearnCardFromBundle('./learncard-export.zip', {
    password: 'use-a-strong-password',
    init: { network: true },
});

console.log(restored.id.did()); // identical to the original wallet's DID
```

{% hint style="info" %}
Restore recreates your **identity**. It does not re-upload your credentials or rebuild your index — for that, use **import** (next step). Use restore when you want to get back into your original account; use import when you want to copy credentials into a different account.
{% endhint %}

***

## 4. Import into another account

Use **import** when you want to copy your credentials and presentations into a **different, fresh wallet**. Import uploads each credential to the target wallet's storage and recreates its index records.

```typescript
import { initLearnCard } from '@learncard/init';
import { importLearnCardBundle } from '@learncard/holder-continuity';

const freshWallet = await initLearnCard({ seed: '0'.repeat(64), network: true });

const report = await importLearnCardBundle('./learncard-export.zip', {
    password: 'use-a-strong-password',
    wallet: freshWallet,
    verifyBeforeImport: true,
});

console.log(report.importedCredentials); // how many credentials were copied
console.log(report.importedPresentations); // how many presentations were copied
console.log(report.errors); // any entries that failed
```

{% hint style="warning" %}
**Verify what you import.** Set `verifyBeforeImport: true` to check each credential's and presentation's signatures before they are uploaded. Without it, import only proves the file decrypted correctly — not that the credentials are genuinely signed. **Only import bundles from sources you trust.**
{% endhint %}

***

## When to use which

| Your goal                                  | Use                          |
| ------------------------------------------ | ---------------------------- |
| Save a backup of everything                | `exportLearnCardBundle`      |
| See what's inside a backup                 | `readLearnCardBundle`        |
| Get my **original account** back           | `restoreLearnCardFromBundle` |
| Copy my credentials into a **new account** | `importLearnCardBundle`      |

***

## Related

{% content-ref url="/pages/vo4U0efBxnYo4VljwlHK" %}
[Holder Continuity](/core-concepts/holder-continuity)
{% endcontent-ref %}

{% content-ref url="/pages/vrw1NENer5dpMHMqRPji" %}
[LearnCard CLI](/sdks/learncard-cli)
{% endcontent-ref %}


# Create Signing Authority

How-To Guide: Configuring a Signing Authority

This guide provides practical, step-by-step recipes for setting up a [Signing Authority](/core-concepts/identities-and-keys/signing-authorities). A [Signing Authority](/core-concepts/identities-and-keys/signing-authorities) is a service that cryptographically signs credentials on your behalf, allowing you to issue official records without directly handling private keys in your application.

We'll cover two paths: the simple, recommended approach of using a LearnCard-managed authority, and the advanced option of registering your own external service.

This guide assumes you have a LearnCard Passport profile. If not, you can create one via the UI or CLI.

## 1. The Simple Path: Using a LearnCard-Managed Authority (Recommended)

**Goal:** Create a secure signing mechanism in under a minute, without managing any keys or infrastructure.

This is the fastest and most common path, perfect for getting your issuance workflow up and running immediately. We handle the complexity so you can focus on your product.

### **Recipe 1a: Using the UI (The Quickest Start)**

1. Navigate to your **Profile** in the LearnCard App.
2. Go to **Developer Tools > Signing Authorities**.
3. Click **"Create New Authority"**.
4. Give it a memorable name (e.g., `default-issuer`) and click **"Create"**.

That's it. If this is your first authority, we automatically set it as your primary. You can now issue credentials using the Universal Inbox, and we'll handle the signing automatically.

### **Recipe 1b: Using the CLI**

For developers who prefer to script their setup, the CLI provides a fast and repeatable way to achieve the same result.

{% hint style="info" %}
To launch the CLI:

```bash
npx @learncard/cli
# Optionally specify a deterministic seed to instantiate the wallet with
# npx @learncard/cli 1b498556081a298261313657c32d5d0a9ce8285dc4d659e6787392207e4a7ac2h
```

{% endhint %}

```javascript
// This script assumes you have an authenticated `learnCard` client instance.

// First, ensure you have a profile. This only needs to be done once.
// await learnCard.invoke.createProfile({ profileId: 'my-org', isServiceProfile: true });

// 1. Create a new signing authority managed by the LearnCard App.
//    We generate and securely store the keys for you.
const managedAuthority = await learnCard.invoke.createSigningAuthority('default-issuer');

if (!managedAuthority) throw new Error('Could not create signing authority.');
// returns -> { name: 'default-issuer', did: 'did:key:z...', endpoint: 'https://...' }

// 2. Register this new authority with the LearnCard Network.
//    This authorizes it to issue credentials on your profile's behalf.
await learnCard.invoke.registerSigningAuthority(
    managedAuthority.endpoint,
    managedAuthority.name,
    managedAuthority.did
);

// 3. (Optional but Recommended) Set it as your primary authority.
//    This allows you to omit signing details from your API calls.
await learnCard.invoke.setPrimaryRegisteredSigningAuthority(
    managedAuthority.endpoint,
    managedAuthority.name
);

console.log('Successfully created and registered primary signing authority!');
```

**Result:** You now have a default Signing Authority. When you call the [`/inbox/issue` endpoint](/how-to-guides/send-credentials) with an unsigned credential, our system will automatically use this authority to sign it. You don't need to specify any `signingAuthority` details in your API call `configuration` object.

## 2. The Advanced Path: Using Your Own External Authority

**Goal:** Delegate credential signing to your own, self-hosted VC-API compliant service for maximum control over your keys and infrastructure.

This path is for organizations with specific security, compliance, or existing identity infrastructure needs.

**Prerequisites:** You must have a running, publicly accessible VC-API compliant issuer endpoint.

### **Recipe: Registering an External Authority**

You don't create an external authority through our system; you simply tell our network about it and authorize it to act on your behalf.

```javascript
// This script assumes you have an authenticated `learnCard` client instance.

// The details of YOUR external signing service.
const myExternalAuthority = {
    name: 'my-custom-signer',
    endpoint: 'https://my-vc-api.my-org.com/issue',
    did: 'did:web:my-org.com', // The DID of your external service
};

// 1. Register your external authority with the LearnCard Network.
await learnCard.invoke.registerSigningAuthority(
    myExternalAuthority.endpoint,
    myExternalAuthority.name,
    myExternalAuthority.did
);

console.log(`Successfully registered "${myExternalAuthority.name}".`);

// You can also set this as your primary authority if desired.
// await learnCard.invoke.setPrimaryRegisteredSigningAuthority(
//   myExternalAuthority.endpoint,
//   myExternalAuthority.name
// );
```

**Result:** Your external service is now an authorized signer for your profile. When you want to use it, you must explicitly specify it in your `/inbox/issue` API call.

**Example `/inbox/issue` call using your external authority:**

```javascript
// Note the explicit `signingAuthority` object in the configuration.
await learncardApiClient.post('/inbox/issue', {
    recipient: {
        /* ... */
    },
    credential: {
        /* ...unsigned credential data... */
    },
    configuration: {
        signingAuthority: {
            name: 'my-custom-signer',
            endpoint: 'https://my-vc-api.my-org.com/issue',
        },
    },
});
```

## Generate a Signing Authority in LearnCardApp

### Steps to Create a Signing Authority

1. **Navigate to Your Profile:**
   * Go to **Developer Tools** > **Signing Authority**.
2. **Create:**
   * **Click**: **Create Signing Authority**
   * **Provide the Following Information:**
     * **Name** (required)
     * **Endpoint** (optional)
     * DID (Endpoint required)
   * **Click**: Create
3. Already Signed In? Deep link below 👇

* [LearnCardApp Signing Authority DevTools](https://learncard.app/passport?showSigningAuthorityDevTools=true)

{% embed url="<https://www.loom.com/share/080838131d82428289073699d19a2aa8>" %}


# Interoperate with LearnCard

How-To Guide: Interoperating with LearnCard

This guide provides practical, standards-based recipes for sending credentials to and receiving credentials from a LearnCard Passport. Our platform is built on open standards like [VC-API](https://w3c-ccg.github.io/vc-api) to ensure seamless interoperability across the ecosystem.

This guide is for developers who want to enable their applications to interact with LearnCard or any other standards-compliant wallet.

## 1. Sending a Credential TO a LearnCard Passport

There are two primary, standards-based methods for sending a credential to a user's passport.

### **Recipe 1a: Sending via a VC-API Deep Link (Simple Method)**

**Goal:** Your application has issued a credential, and you want to give your user a simple "Send to LearnCard" button to add it to their passport.

**Method:** We will use a **VC-API Deep Link**. This is the simplest way to initiate an issuance flow, as it puts the responsibility on the LearnCard app to "pull" the credential from your server.

**The Recipe:**

1. **Create a** [**VC-API Endpoint**](https://w3c-ccg.github.io/vc-api)**:** Your server must expose an endpoint that can initiate a VC-API exchange. When LearnCard calls this endpoint, it should respond with the credential you want to send.
2. **Construct the Deep Link:** In your application's UI, create a link or button that points to the LearnCard deep link format. The `vc_request_url` parameter should be the URL of your endpoint from Step 1.
3. **Redirect the User:** When the user clicks the link, they will be taken to their LearnCard Passport, which will automatically initiate the exchange with your server to claim the credential.

**Example:**

<pre class="language-javascript"><code class="lang-javascript">=== "JavaScript (Frontend)"
<strong>// Your server has an endpoint ready at https://api.my-app.com/issue/xyz 
</strong><strong>const vcApiEndpoint = 'https://api.my-app.com/issue/xyz';
</strong>
// Construct the deep link
const learnCardDeepLink = `https://learncard.app/request?vc_request_url=${encodeURIComponent(vcApiEndpoint)}`;

// When a user clicks your "Send to LearnCard" button, redirect them:
window.location.href = learnCardDeepLink;

</code></pre>

**What Happens:**

* The user is seamlessly transitioned to their LearnCard Passport.
* LearnCard automatically makes a request to your `vc_request_url`, follows the VC-API exchange protocol, and pulls the credential into the user's passport.

### **Recipe 1b: Sending via an Interaction URL (Universal Method)**

{% embed url="<https://www.loom.com/share/6c4aef61e68d4f0bb3ec92d23e8fb6b1?sid=290d3fa1-cd46-4153-8e42-185cd00366e6>" %}
Video showing creating a VC-API interaction URL, and claiming it in LearnCard Native App using VC-API exchange flow.
{% endembed %}

**Goal:** You want to offer a credential via a QR code or link that *any* standards-compliant wallet can use, providing maximum interoperability.

**Method:** We will use a standard [**Interaction URL**](https://w3c-ccg.github.io/vc-api/#interaction-url-format). This is a two-step process: the wallet first identifies itself, and then you send it the credential.

**The Recipe:**

1. **Generate an Interaction URL:** Create a unique URL for the issuance, ending in `?iuv=1`. Display this as a link or QR code.
2. **Serve Protocols:** When a wallet `GET`s this URL, respond with your `vcapi` exchange endpoint.
3. **Request Identity:** When the wallet `POST`s to your exchange endpoint, your server must first ask "Who are you?". You do this by responding with a `VerifiablePresentationRequest` for a `DIDAuthentication` presentation.
4. **Issue the Credential:** Once the wallet sends back the `DidAuth` presentation, your server verifies it, extracts the holder's DID, and can now respond with a final `VerifiablePresentation` containing the credential.

**Example:**

<pre class="language-javascript"><code class="lang-javascript">=== "Node.js (Server-Side)"
<strong>// Example using Express.js to handle a multi-step issuance exchange
</strong>
// 1. A QR code on your site points to: https://api.my-app.com/issue-offer/xyz?iuv=1

// 2. Handle the initial GET from the wallet for protocol discovery
app.get('/issue-offer/xyz', (req, res) => {
  if (req.headers.accept === 'application/json') {
    return res.json({
      protocols: {
        vcapi: 'https://api.my-app.com/vc-api/issue-exchange/xyz'
      }
    });
  }
  res.send('Please scan this with a digital passport to claim your credential.');
});

// 3 &#x26; 4. Handle the multi-step VC-API exchange
app.post('/vc-api/issue-exchange/xyz', async (req, res) => {
  // Check if the wallet is responding with its identity (DidAuth VP)
  if (req.body.verifiablePresentation &#x26;&#x26; req.body.verifiablePresentation.proof) {
    // Step 4: Wallet has identified itself.
    // You must verify the DidAuth presentation to get the holder's DID securely.
    const holderDid = await verifyDidAuth(req.body.verifiablePresentation);

    // Now, create the credential specifically for that holder
    const issuedCredential = createCredentialFor(holderDid); // Your logic here

    // Respond with the final credential
    return res.status(200).json({
      verifiablePresentation: {
        '@context': ['https://www.w3.org/2018/credentials/v1'],
        type: ['VerifiablePresentation'],
        verifiableCredential: [issuedCredential]
      }
    });

  } else {
    // Step 3: This is the first POST from the wallet. Request its identity.
    const didAuthRequest = {
      verifiablePresentationRequest: {
        query: [{ type: "DIDAuthentication" }],
        challenge: "a-unique-challenge-for-this-session",
        domain: "my-app.com"
      }
    };
    return res.status(200).json(didAuthRequest);
  }
});
</code></pre>

## 2. Receiving a Credential FROM a LearnCard Passport

**Goal:** You want to verify a user's credential to grant them access to a service, confirm a skill, or pre-fill a form.

**Method:** We will use a standard [**Interaction URL**](https://w3c-ccg.github.io/vc-api/#interaction-url-format) to request a credential from the user. This is typically presented as a QR code or a clickable link.

**The Recipe:**

1. **Generate an Interaction URL:** For each verification session, your server should generate a unique URL. This URL must follow the standard, ending in `?iuv=1`.
2. **Serve a Protocols Response:** When a wallet (like LearnCard) performs an HTTP `GET` on this Interaction URL with an `Accept: application/json` header, your server must respond with a JSON object listing the supported protocols. The key is the `vcapi` protocol, which points to your exchange endpoint.
3. **Initiate the Exchange:** The wallet will then make a `POST` request to your `vcapi` service endpoint. Your server's first move in this exchange is to respond with a `VerifiablePresentationRequest`, asking for the specific credential you need.
4. **Receive and Verify:** The wallet will respond with a `VerifiablePresentation` containing the credential. Your server can then verify its authenticity and grant access.

**Example:**

````javascript
=== "Node.js (Server-Side)" 
/ Example using Express.js to handle the interaction
// 1. A user visits your verification page, which displays a QR code
//    pointing to: https://api.my-app.com/verify/session-123?iuv=1

// 2. Your server handles the GET request from the wallet
app.get('/verify/session-123', (req, res) => {
  // Check for the standard content negotiation header
  if (req.headers.accept === 'application/json') {
    // Respond with the supported protocols
    return res.json({
      protocols: {
        // This is the endpoint where the actual exchange will happen
        vcapi: 'https://api.my-app.com/vc-api/exchange/session-123'
      }
    });
  }
  // If it's a regular browser, show a human-readable page
  res.send('Please scan this with LearnCard.');
});

// 3. The wallet POSTs to your exchange endpoint to start the flow
app.post('/vc-api/exchange/session-123', (req, res) => {
  // For the first interaction, we request the credential we need.
  const presentationRequest = {
    verifiablePresentationRequest: {
      query: [{
        type: "QueryByExample",
        credentialQuery: [{
          reason: "We need to verify your 'Advanced JavaScript' certificate.",
          example: {
            type: "CertificateOfCompletion",
            credentialSubject: { course: "Advanced JavaScript" }
          }
        }]
      }],
      challenge: "a-unique-challenge-string",
      domain: "my-app.com"
    }
  };
  res.status(200).json(presentationRequest);
});
```

````

## 3. How LearnCard's Universal Inbox is Interoperable

**Goal:** Understand how the `claimUrl` you receive from the [Universal Inbox API](/how-to-guides/send-credentials) can be used by *any* standards-compliant wallet, not just LearnCard.

**Method:** The `claimUrl` is simply a pre-built **Interaction URL**. It follows the exact same content negotiation standard described in Recipe 2.

**The Recipe:**

An external wallet application can treat the `claimUrl` as an interoperable endpoint.

1. **Fetch with Content Negotiation:** The wallet performs an HTTP `GET` on the `claimUrl`, but with a crucial difference: it sets the `Accept` header to `application/json`.
2. **Receive Protocols:** The LearnCard server will detect this header and respond with the JSON `protocols` object, including the `vcapi` endpoint where the credential claim exchange can be initiated.
3. **Initiate VC-API Exchange:** The external wallet can then proceed with the standard VC-API flow at the provided endpoint to claim the credential on behalf of its user.

**Example:**

````bash
=== "cURL (Wallet Simulation)"
# An external wallet performing content negotiation on a LearnCard claim URL
# The claim URL you received from the Universal Inbox API:
# https://learncard.app/interactions/abc123xyz?iuv=1

curl -X GET "https://learncard.app/interactions/abc123xyz?iuv=1" \
  -H "Accept: application/json"

# The LearnCard server responds with the protocols:
# {
#   "protocols": {
#     "vcapi": "https://api.learncard.com/v1/workflows/inbox-claim/exchanges/abc123xyz",
#     "website": "https://learncard.app/interactions/abc123xyz"
#   }
# }
```

````

**What Happens:** The external wallet now has the direct VC-API endpoint it needs to start the claim flow. This demonstrates how the Universal Inbox is not a closed system, but a powerful issuance tool built on top of open, interoperable standards.


# Verify My Issuer

How-To Guide: Join the LEF Trusted Issuer Registry

This guide provides step-by-step instructions for organizations wishing to be recognized as trusted issuers within the LearnCard ecosystem by being added to the LEF Member Trusted Issuer Registry. The process involves submitting your organization's details via a Pull Request (PR) to our open-source LearnCard GitHub repository.

**Goal:** To successfully add your organization's DID and information to the `registry.json` file, making it a recognized issuer.

**Who is this for?** Organizations that issue or plan to issue Verifiable Credentials and wish to be listed in the LEF's primary trusted issuer registry.

{% hint style="info" %}
What is a Trusted Issuer Registry? [Click here to learn more in the Core Concepts documentation](/core-concepts/identities-and-keys/trust-registries).
{% endhint %}

**What you'll need:**

* A **GitHub account**.
* Basic familiarity with **Git and GitHub Pull Requests**. If you're new to this, GitHub provides excellent [guides](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request).
* Your organization's **official name**.
* Your organization's primary **website URL**.
* Your organization's **location** (City, State/Region, Country).
* The **Decentralized Identifier (DID)** your organization will use for issuing credentials (e.g., `did:web:yourdomain.com` or `did:key:z...`).
* A brief **description** of your organization and its role or purpose in issuing credentials.
* (Optional but Recommended) A URL to your organization's **governance document** or public statement regarding its credentialing practices.
* A **contact person** (GitHub username or email) for any questions regarding your submission.

## Procedure: Adding Your Organization to the Registry

Follow these steps carefully to submit your petition:

### **Step 1: Prepare Your Organization's Information**

Before you start, gather all the details listed in the "What you'll need" section above. Ensure they are accurate and official. The DID you provide should be the primary DID your organization will use as an issuer.

### **Step 2: Navigate to the Registry File on GitHub**

The LEF Member Trusted Issuer Registry is a JSON file located in the LearnCard repository.

* **Click this link to go directly to the file:** [https://github.com/learningeconomy/LearnCard/blob/main/packages/learn-card-registries/trusted/registry.json](https://github.com/learningeconomy/LearnCard/tree/main/packages/learn-card-registries/trusted/registry.json)

### **Step 3: Edit the `registry.json` File**

1. On the GitHub page for `registry.json`, click the **pencil icon** (Edit this file) in the upper right corner of the file view.
   * If you do not have direct write access to the repository, GitHub will automatically help you **fork the repository**. This creates a personal copy of the LearnCard repository under your GitHub account where you can make changes. Click "Fork this repository and propose changes."
2. You are now in the GitHub file editor. The `registry.json` file contains a main `registry` object, which holds key-value pairs. Each key is an issuer's DID, and the value is an object containing their details.
3. **Carefully add a new entry for your organization** within the `registry` object. Find a logical place (e.g., alphabetical by DID, though exact order isn't strictly enforced by the format, it helps readability).

   * Ensure your entry follows the existing JSON structure.
   * The key for your entry **must be your organization's DID**.
   * The value must be an object with `name`, `location`, and `url` keys.

   **Example Entry Structure:**

   ```json
   "YOUR_ORGANIZATIONS_DID_HERE": {
       "name": "Your Official Organization Name",
       "location": "City, State/Region, Country",
       "url": "https://yourorganization.com/"
   }
   ```
4. **Make sure to add a comma (`,`) after the preceding entry if yours is not the last one in the list.**

   **Example of adding an entry:** If the file looks like this:

   ```json
   {
       "meta": { ... },
       "registry": {
           "did:web:existing.org": {
               "name": "Existing Org",
               "location": "Some City, Country",
               "url": "https://existing.org/"
           } 
           // <--- Add your entry after this, so add a comma above
       }
   }
   ```
5. **You would add your entry like this:**

   ```json
   {
       "meta": { ... },
       "registry": {
           "did:web:existing.org": {
               "name": "Existing Org",
               "location": "Some City, Country",
               "url": "https://existing.org/"
           }, // <--- Added comma here
           "did:web:yourneworg.com": { // <--- Your new entry
               "name": "My New Organization",
               "location": "New City, New Country",
               "url": "https://yourneworg.com/"
           }
       }
   }

   ```
6. **Validate your JSON:** Double-check your syntax. Missing commas, quotes, or brackets will make the JSON invalid. You can use an online [JSON validator to check your changes](https://jsonlint.com/) before committing if you're unsure.

### **Step 4: Propose Changes (Commit)**

1. Once you've added your entry and verified the JSON is correct, scroll down below the file editor.
2. You'll see a section titled "**Propose changes**" (if you forked) or "**Commit changes**" (if you have write access and are working on a branch).
3. Enter a concise and descriptive **commit message**. For example: `feat: Add [Your Organization Name] to LEF Trusted Issuer Registry`
4. You can add an optional extended description if needed.
5. If you forked, GitHub will typically select "Create a new branch for this commit and start a pull request." This is good. Give your branch a descriptive name (e.g., `add-[your-org-name]-to-registry`).
6. Click the green "**Propose changes**" or "**Commit changes**" button.

### **Step 5: Open a Pull Request**

If you forked and created a new branch, GitHub will usually take you to a page to "Open a pull request." If not, navigate to your fork of the `learningeconomy/LearnCard` repository, select your new branch, and click the "Contribute" button, then "Open pull request."

1. **Base Repository/Branch:** Ensure the base repository is `learningeconomy/LearnCard` and the base branch is `main`.
2. **Head Repository/Branch:** Ensure this is your fork and the branch where you made your changes.
3. **Title:** The PR title should be clear, similar to your commit message (e.g., `Add [Your Organization Name] to LEF Trusted Issuer Registry`).
4. **Description:** This is crucial. **Please use the template below** to provide all necessary information for the review team. Copy and paste this template into the PR description box and fill in your details:

   ```markdown
   ### Organization Information

   * **Official Organization Name:** [Your Full Official Organization Name]
   * **Organization Website:** [Link to your organization's primary website]
   * **Organization Location:** [City, State/Region, Country]
   * **Primary Contact for this Request (GitHub Username or Email):** [@your-github-username or your@email.com]

   ### DID Information

   * **DID to be Added to Registry:** `YOUR_ORGANIZATIONS_DID_HERE` 
       *(Please ensure this is the exact DID you added to registry.json)*

   ### Purpose & Governance

   * **Brief Description of Organization:** [Provide a 1-2 sentence description of your organization and its mission.]
   * **Reason for Joining the Registry / Role in Credential Ecosystem:** [Explain why your organization issues credentials and wishes to be recognized as a trusted issuer. What types of credentials will you issue?]
   * **(Optional) Link to Governance Document / Public Issuer Statement:** [If you have a public document outlining your credentialing policies, or a statement about your role as an issuer, please link it here.]

   ### Verification

   * [ ] I have verified that the DID provided is correct and is controlled by my organization.
   * [ ] I have verified that the information added to `registry.json` is accurate and the JSON format is valid.
   * [ ] I understand that this PR will be reviewed by the Learning Economy Foundation team and inclusion is subject to their approval based on the registry's governance.

   ### Additional Notes (Optional)

   [Any other relevant information for the reviewers.]

   ```

### **Step 6: Submit the Pull Request**

* Review your PR details one last time.
* Click the green "**Create pull request**" button.

### What Happens Next?

1. **Review:** Your Pull Request will be reviewed by members of the Learning Economy Foundation team. They may ask questions or request clarifications via comments on the PR.
2. **Discussion:** Please monitor your PR for any feedback and respond promptly.
3. **Approval & Merge:** If your submission meets the criteria and all information is verified, your PR will be approved and merged. Your organization will then be part of the LEF Member Trusted Issuer Registry!
4. **Propagation:** Once merged, it may take some time for applications and services consuming the registry to pick up the latest version.

### **Important Considerations:**

* **JSON Validity:** The most common issue is invalid JSON. Please double-check your syntax, especially commas and quotes.
* **Accuracy:** Ensure all provided information, especially your DID and website URL, is accurate.
* **Patience:** The review process may take some time. Your cooperation in providing clear information will help expedite it.

Thank you for your interest in becoming a trusted issuer in the LearnCard ecosystem! We look forward to your contribution.


# Connect Systems


# Connect a Website

How-To Guide: Issue Digital Badges from Your Website

Want to award your users verifiable digital badges for their achievements on your website or platform? This tutorial will show you how! We'll explore two main approaches and then dive deep into a robust, integrated solution using LearnCard's ConsentFlow.

**Our Example Use Case: EduPlatform** Imagine "EduPlatform," an online learning site. When a student successfully completes the "JavaScript Fundamentals" course, EduPlatform wants to automatically issue them a digital badge they can store in their LearnCard wallet and share anywhere.

## **What you'll accomplish:**

* Understand two methods for issuing badges.
* Set up your platform (EduPlatform) as a LearnCard Issuer.
* Create a "ConsentFlow Contract" so users can connect their LearnCard accounts to EduPlatform.
* Implement a "Connect LearnCard" button on your website.
* Handle the user consent process and securely store their LearnCard DID.
* Automatically issue a "Course Completion" badge (as a Verifiable Credential) to a user when they complete an activity on your site, using the established consent.

## Choosing Your Badge Issuance Method

There are a couple of ways to get badges to your users:

### **Option 1: Quick & Simple - Claimable Links/QR Codes (via Boosts)**

* **How it works:** You create a "Boost" (a master template for your badge). From this Boost, you generate a unique claim link or QR code. You can display this QR code at the end of a workshop, email the link, etc. Users scan/click it to claim their badge.
* **Pros:** Very easy to set up, no deep integration needed, great for one-off events or when users don't have accounts on your platform.
* **Cons:** Less automated for ongoing achievements tied to user accounts on your site; relies on the user to actively claim.
* ➡️ **If this sounds like what you need, head over to our** [**Quickstart: Create a Claimable Boost Tutorial for a step-by-step guide**](/quick-start/your-first-integration)**!**

### **Option 2: Automated & Integrated - Connecting LearnCard Accounts (This Guide's Focus)**

* **How it works:** Users explicitly connect their LearnCard account to your website by consenting to a "ConsentFlow Contract." This contract gives your website permission to issue specific types of badges directly to their LearnCard wallet when they achieve something.
* **Pros:** Fully automated issuance, badges are tied to their account activity on your platform, enables ongoing recognition, allows for personalized badge data.
* **Cons:** Requires a bit more setup for the initial consent flow.
* ➡️ **If you want this deeper, automated integration, you're in the right place! Let's proceed.**

#### Prerequisites for the Integrated Approach

* **LearnCard SDK Initialized (for your Backend):** Your website's backend will need an active `learnCard` instance connected to the network.
* **Service Profile for Your Website:** Your website/platform will need its own identity on the LearnCard Network.
* **Basic Understanding:** Familiarity with [DIDs](/core-concepts/identities-and-keys/decentralized-identifiers-dids), [Verifiable Credentials (VCs),](/core-concepts/credentials-and-data/verifiable-credentials-vcs) [Boosts](/core-concepts/credentials-and-data/boost-credentials), and the [Create a ConsentFlow Tutorial](/tutorials/create-a-consentflow) will be very helpful.
* **Web Development Setup:**
  * A backend environment (e.g., Node.js with Express) to handle API calls and user data.
  * A frontend (simple HTML/JS is fine for this guide) for the user interface.

{% hint style="success" %}
You can skip **Part 1 & 2** by configuring your issuer and creating your ConsentFlow contract using the built-in UI:
{% endhint %}

{% embed url="<https://www.loom.com/share/c4524abf747c4d6f8e3a67821c25050d>" %}

## Part 1: Setting Up Your Platform (EduPlatform as Issuer)

Your website's backend needs to act as an Issuer.

### **Step 1.1: Initialize LearnCard SDK on Your Backend**

This instance will represent EduPlatform.

```typescript
// backend/learncard-setup.ts
import { initLearnCard } from '@learncard/init';

// IMPORTANT: Store your seed securely (e.g., environment variable in production)
const EDUPLATFORM_ISSUER_SEED = 'your-eduplatform-secure-hex-seed-string'; // Replace!

let networkLearnCard; // To store the initialized SDK instance

export async function getEduPlatformLearnCard() {
    if (networkLearnCard) return networkLearnCard;

    console.log('Initializing EduPlatform LearnCard SDK...');
    networkLearnCard = await initLearnCard({
        seed: EDUPLATFORM_ISSUER_SEED,
        network: true,     // Connect to the LearnCard Network
        allowRemoteContexts: true, // Often needed for VC contexts
    });
    console.log("EduPlatform LearnCard Initialized.");
    console.log("EduPlatform Issuer DID:", networkLearnCard.id.did());
    return networkLearnCard;
}

```

### **Step 1.2: Ensure EduPlatform Has a Service Profile**

Your EduPlatform needs an identity on the LearnCard Network.

```typescript
// backend/profile-manager.ts
import { getEduPlatformLearnCard } from './learncard-setup';

const EDUPLATFORM_PROFILE_ID = 'eduplatform-main';
const EDUPLATFORM_DISPLAY_NAME = 'EduPlatform Online Learning';

export async function ensureEduPlatformProfile() {
    const learnCard = await getEduPlatformLearnCard();
    try {
        let profile = await learnCard.invoke.getProfile(EDUPLATFORM_PROFILE_ID);
        if (!profile) {
            console.log(`Creating service profile for EduPlatform: ${EDUPLATFORM_PROFILE_ID}`);
            await learnCard.invoke.createServiceProfile({
                profileId: EDUPLATFORM_PROFILE_ID,
                displayName: EDUPLATFORM_DISPLAY_NAME,
                image: 'https://example.com/eduplatform-logo.png' // Optional
            });
            console.log('EduPlatform Service Profile created successfully.');
        } else {
            console.log('EduPlatform Service Profile already exists.');
        }
    } catch (error: any) {
        if (error.message?.includes('Profile ID already exists')) {
            console.log('EduPlatform Service Profile already exists (confirmed by error).');
        } else {
            console.error(`Failed to ensure EduPlatform profile: ${error.message}`);
            // In production, you might want to handle this more gracefully or retry
        }
    }
}

// Call this during your application's startup sequence
// ensureEduPlatformProfile();

```

{% hint style="success" %}
**Action:** Integrate these into your backend's startup process.
{% endhint %}

## Part 2: Creating a ConsentFlow Contract for Badge Issuance

This contract defines that EduPlatform requests permission from users to issue "Achievement" badges to them.

### **Step 2.1: Define the Contract Terms**

```typescript
// backend/consent-contract.ts
import { getEduPlatformLearnCard } from './learncard-setup';

// IMPORTANT: This URL must be an endpoint on YOUR website that can handle the redirect
const YOUR_WEBSITE_CONSENT_CALLBACK_URL = 'https://eduplatform.example.com/auth/learncard/callback';

const badgeIssuanceConsentContract = {
    name: "EduPlatform Badge Program",
    subtitle: "Receive digital badges for your course completions!",
    description: "Connect your LearnCard to allow EduPlatform to automatically issue you verifiable badges when you complete courses and achieve milestones.",
    image: "https://example.com/eduplatform-badge-program.png", // Optional
    contract: {
        read: { // What EduPlatform wants to read (optional for this use case)
            personal: {
                // We could request 'Name' to personalize badges, but let's keep it simple.
                // Name: { required: false } 
            }
        },
        write: { // What EduPlatform wants to write (issue) to the user
            credentials: {
                categories: {
                    // We will be issuing "Achievement" badges
                    "Achievement": { required: true } 
                }
            }
        }
    },
    redirectUrl: YOUR_WEBSITE_CONSENT_CALLBACK_URL 
};

let contractUriCache: string | null = null;

export async function getOrCreateBadgeConsentContractUri(): Promise<string> {
    if (contractUriCache) return contractUriCache;

    const learnCard = await getEduPlatformLearnCard();
    try {
        // Optional: Check if a contract with a similar name/purpose already exists to avoid duplicates
        // For this tutorial, we'll assume we create it if we don't have a URI cached.
        console.log('Creating EduPlatform Badge ConsentFlow Contract...');
        const uri = await learnCard.invoke.createContract(badgeIssuanceConsentContract);
        console.log('ConsentFlow Contract Created! URI:', uri);
        contractUriCache = uri; // Cache it for subsequent calls
        return uri;
    } catch (error) {
        console.error('Error creating ConsentFlow contract:', error);
        throw error;
    }
}

```

**Action:** Ensure your backend can create this contract (perhaps on startup or via an admin function) and store the `contractUri`.

## Part 3: Enabling Users to Connect Their LearnCard on Your Website

### **Step 3.1: Frontend - The "Connect LearnCard" Button**

On EduPlatform's user profile page or settings page, add a button.

```html
<div>
    <h2>LearnCard Integration</h2>
    <p>Connect your LearnCard to automatically receive digital badges for your achievements!</p>
    <button id="connectLearnCardBtn">Connect Your LearnCard</button>
</div>

<script>
    // This script would run on your frontend
    const connectButton = document.getElementById('connectLearnCardBtn');

    connectButton.addEventListener('click', async () => {
        try {
            // In a real app, you'd fetch this URI from your backend
            // For demo, we assume it's available or hardcoded after backend creates it.
            const eduPlatformContractUri = 'uri:contract:YOUR_EDUPLATFORM_CONTRACT_URI'; // Replace!
            const userReturnUrl = 'https://eduplatform.example.com/dashboard?learncard_connected=true'; // Where user lands on your site after consenting

            if (eduPlatformContractUri === 'uri:contract:YOUR_EDUPLATFORM_CONTRACT_URI') {
                alert('Error: Contract URI not set. Backend might not have created it yet.');
                return;
            }
            
            const consentUrl = `https://learncard.app/consent-flow?uri=${encodeURIComponent(eduPlatformContractUri)}&returnTo=${encodeURIComponent(userReturnUrl)}`;
            
            console.log('Redirecting to LearnCard for consent:', consentUrl);
            window.location.href = consentUrl; // Redirect user to LearnCard consent page
        } catch (error) {
            console.error('Error preparing consent URL:', error);
            alert('Could not initiate LearnCard connection. Please try again.');
        }
    });
</script>

```

{% hint style="success" %}
**Action:** Implement this button. When clicked, it redirects the user to `learncard.app` to approve the connection based on your contract.
{% endhint %}

## Part 4: Handling the Consent Callback & Storing User DIDs

After the user consents on `learncard.app`, they are redirected back to the `redirectUrl` you specified in your contract (or the `returnTo` URL from the consent link).

### **Step 4.1: Backend - Create a Callback Endpoint**

Your backend (e.g., EduPlatform's Node.js/Express server) needs an endpoint to handle this.

```typescript
// backend/routes/authRoutes.ts (example using Express.js)
// import express from 'express';
// const router = express.Router();

// This would be your endpoint: e.g., GET https://eduplatform.example.com/auth/learncard/callback
// router.get('/learncard/callback', async (req, res) => {
async function handleLearnCardCallback(req, res) { // req, res are Express request/response
    const userDid = req.query.did as string;

    if (userDid) {
        console.log(`LearnCard consent callback received for User DID: ${userDid}`);
        
        // TODO:
        // 1. Validate this request (e.g., check a state parameter if you sent one)
        // 2. Get your application's internal user ID (e.g., from the current session)
        const internalUserId = req.session?.userId; // Example: if using sessions

        if (!internalUserId) {
            // res.status(400).send('User session not found. Please log in to EduPlatform first.');
            console.error('User session not found during LearnCard callback.');
            return; // Or handle error appropriately
        }

        // 3. Securely store the userDid in your database, associating it with internalUserId
        //    Example: await db.users.update({ id: internalUserId }, { learnCardDid: userDid });
        console.log(`ACTION: Store LearnCard DID ${userDid} for EduPlatform user ${internalUserId}`);
        
        // 4. Redirect the user to a success page on your platform
        //    res.redirect('/dashboard?learncard_connected=true');
        console.log('Redirecting user to their dashboard...');
    } else {
        console.error('LearnCard consent callback error: DID not found in query parameters.');
        // res.status(400).send('LearnCard connection failed or was denied.');
    }
}
// export default router;

```

{% hint style="success" %}
**Action:** Implement this callback endpoint on your backend. Securely associate the received `userDid` with the logged-in user on your platform.
{% endhint %}

## Part 5: Issuing a Badge When a User Completes an Activity

Let's say a user completes the "JavaScript Fundamentals" course on EduPlatform.

### **Step 5.1: Create a Boost for Your Badge (One-time Setup)**

It's best practice to create a "Boost" to act as the template for your "JavaScript Fundamentals Completion" badge. This makes managing and issuing it easier.

```typescript
// backend/badge-manager.ts
import { getEduPlatformLearnCard } from './learncard-setup';
import { randomUUID } from 'crypto'; // Node.js built-in

const JS_FUNDAMENTALS_BOOST_NAME = 'JavaScript Fundamentals Completion Badge';
let jsFundamentalsBoostUri: string | null = null;

export async function getOrCreateJsFundamentalsBoost(): Promise<string> {
    if (jsFundamentalsBoostUri) return jsFundamentalsBoostUri;

    const learnCard = await getEduPlatformLearnCard();
    
    const badgeTemplate = {
        "@context": [
            "https://www.w3.org/2018/credentials/v1",
            "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.1.json", // Open Badges context
            "https://ctx.learncard.com/boosts/1.0.3.json" // LearnCard Boosts context
        ],
        type: ["VerifiableCredential", "OpenBadgeCredential", "BoostCredential"],
        name: JS_FUNDAMENTALS_BOOST_NAME, // Name of the credential instances
        credentialSubject: {
            // 'id' will be the recipient's DID, filled in during issuance
            achievement: {
                achievementType: "Badge",
                criteria: { narrative: "Awarded for demonstrating foundational knowledge of JavaScript programming." },
                description: "This badge certifies the successful completion of the JavaScript Fundamentals course on EduPlatform.",
                id: "urn:uuid:" + randomUUID(), // Unique ID for this achievement definition
                image: "https://example.com/badges/js-fundamentals.png",
                name: "JavaScript Fundamentals Completion",
                type: ["Achievement"]
            },
            type: ["AchievementSubject"]
        },
        display: { backgroundColor: "#F9A825", displayType: "badge" }, // Example display
        image: "https://example.com/badges/js-fundamentals.png"
    };

    const boostMetadata = {
        name: JS_FUNDAMENTALS_BOOST_NAME, // Name of the Boost template itself
        description: 'Awards a badge for completing the JavaScript Fundamentals course.',
        category: 'Achievement'
    };

    try {
        // In a real app, you might search for an existing Boost by name/metadata first.
        console.log('Creating JavaScript Fundamentals Boost template...');
        // `createBoost` expects the credential template itself, not a pre-signed VC
        jsFundamentalsBoostUri = await learnCard.invoke.createBoost(badgeTemplate, boostMetadata);
        console.log('JavaScript Fundamentals Boost Created! URI:', jsFundamentalsBoostUri);
        return jsFundamentalsBoostUri;
    } catch (error) {
        console.error('Error creating JavaScript Fundamentals Boost:', error);
        throw error;
    }
}

// Call this during your application's startup or via an admin function
// getOrCreateJsFundamentalsBoost();

```

{% hint style="success" %}
**Action:** Ensure this Boost is created and you store its `jsFundamentalsBoostUri`.
{% endhint %}

### **Step 5.2: Backend Logic - Issue Badge on Course Completion**

When your backend detects a user has completed the course:

```typescript
// backend/course-completion-handler.ts
import { getEduPlatformLearnCard } from './learncard-setup';
import { getOrCreateBadgeConsentContractUri } from './consent-contract';
import { getOrCreateJsFundamentalsBoost } from './badge-manager';
import { randomUUID } from 'crypto';

// This function would be called by your platform's logic when a course is completed.
export async function issueJsBadgeToUser(internalUserId: string, userFullName: string) {
    const learnCard = await getEduPlatformLearnCard();
    
    // 1. Retrieve the user's LearnCard DID (stored in Part 4)
    // const userLearnCardDid = await db.users.getLearnCardDid(internalUserId); // Example DB call
    const userLearnCardDid = 'did:lcn:USER_PROFILE_ID_FROM_YOUR_DB'; // Replace with actual lookup

    if (!userLearnCardDid) {
        console.log(`User ${internalUserId} has not connected their LearnCard. Cannot issue badge.`);
        return;
    }

    try {
        const contractUri = await getOrCreateBadgeConsentContractUri();
        const boostUriToIssue = await getOrCreateJsFundamentalsBoost();

        // 2. Define the specific credential content for THIS user
        //    The `writeCredentialToContract` will use the Boost as a template
        //    but we provide the final `credentialSubject` and other instance-specific details.
        const credentialForUser = {
            "@context": [ // Contexts from your Boost template are often inherited or merged
                "https://www.w3.org/2018/credentials/v1",
                "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.1.json",
                "https://ctx.learncard.com/boosts/1.0.3.json"
            ],
            id: "urn:uuid:" + randomUUID(), // Unique ID for this specific credential instance
            type: ["VerifiableCredential", "OpenBadgeCredential", "BoostCredential", "AchievementCredential"], // Ensure "Achievement" is in your contract's write.credentials.categories
            issuer: learnCard.id.did(), // EduPlatform's DID
            issuanceDate: new Date().toISOString(),
            name: `JavaScript Fundamentals Completion - ${userFullName}`, // Personalized name
            credentialSubject: {
                id: userLearnCardDid, // The recipient's DID
                achievement: { // This structure should align with what your Boost template expects
                    achievementType: "Badge",
                    name: "JavaScript Fundamentals Completion",
                    description: "Successfully completed all modules and assessments for JavaScript Fundamentals.",
                    // You can add more personalized details here if your Boost template supports them
                }
            },
            // Display properties might be inherited from the Boost, or you can specify some here
        };
        
        console.log(`Issuing JS Fundamentals badge to ${userLearnCardDid} via contract ${contractUri} using boost ${boostUriToIssue}`);

        // 3. Issue the credential via the ConsentFlow contract
        const issuedCredentialUri = await learnCard.invoke.writeCredentialToContract(
            userLearnCardDid,   // The DID of the consenting user (recipient)
            contractUri,        // The URI of your ConsentFlow contract
            credentialForUser,  // The specific VC data to issue
            boostUriToIssue     // The URI of the Boost template this credential is an instance of
        );

        console.log(`Badge successfully issued to ${userLearnCardDid}! Credential URI: ${issuedCredentialUri}`);
        // Optionally, notify the user within your EduPlatform UI.
        // The user will also get a notification in their LearnCard app if they have webhooks configured.

    } catch (error) {
        console.error(`Error issuing badge to user ${internalUserId} (DID: ${userLearnCardDid}):`, error);
    }
}

// Example: Simulate a user completing a course
// issueJsBadgeToUser('eduplatform_user_123', 'Jane Doe'); 

```

{% hint style="success" %}
**Action:** Integrate this logic into your platform's course completion flow.
{% endhint %}

## Part 6: User Experience - Viewing the Badge

Once EduPlatform issues the badge:

* The user will receive a notification in their LearnCard app (if they have notifications enabled).
* The "JavaScript Fundamentals Completion" badge will appear in their LearnCard alerts notification tray, ready to be claimed, viewed and shared.

## Summary & Next Steps

Congratulations! You've now outlined a complete system for: ✅ **Setting up** your website (EduPlatform) as a LearnCard Issuer. ✅ **Creating a ConsentFlow Contract** to request permission to issue badges. ✅ Allowing users to **connect their LearnCard accounts** via a button on your site. ✅ **Handling the consent callback** to store user DIDs. ✅ **Automatically issuing a specific badge** (as a Verifiable Credential instance of a Boost) to a user's LearnCard wallet when they complete an action on your site.

This integrated approach provides a seamless experience for your users and a powerful way for your platform to issue verifiable digital recognition.

From here, you can explore:

* Creating a variety of [**Boosts**](/core-concepts/credentials-and-data/boost-credentials) for different badges and achievements.
* Using [**Auto-Boosts**](/core-concepts/consent-and-permissions/auto-boosts) within your ConsentFlow contract to issue an initial badge just for connecting.
* Implementing more detailed **error handling and user feedback** throughout the process.


# Embed a Claim Button

How-To Guide: Add a credential claim button to any website using the LearnCard Embed SDK

Add a "Claim Credential" button to any webpage — a course completion page, an event landing page, an onboarding flow. When a user clicks it, a polished modal walks them through email verification and deposits the credential directly into their LearnCard wallet.

{% hint style="info" %}
This is for **external websites** that want to award credentials to visitors. If you're building an app that runs *inside* the LearnCard App Store, see [Connect an Embedded App](/how-to-guides/connect-systems/connect-an-embedded-app) instead.
{% endhint %}

## Prerequisites

* A LearnCard developer account with an **Embed** integration created in the [Developer Dashboard](https://learncard.app)
* At least one **credential template** attached to that integration
* Your integration's **publishable key** (`pk_...`)

## Step 1: Create Your Integration & Template

1. Go to the Developer Dashboard → **New Integration** → choose **Embed Claim Button**
2. Follow the setup guide: set your partner name, create a credential template
3. Copy your **publishable key** from the Embed Code tab

## Step 2: Add the SDK

{% tabs %}
{% tab title="Script Tag (CDN)" %}

```html
<script src="https://cdn.jsdelivr.net/npm/@learncard/embed-sdk@latest/dist/learncard.js"></script>
```

{% endtab %}

{% tab title="npm/ESM" %}

```bash
npm install @learncard/embed-sdk
```

```js
import { init } from '@learncard/embed-sdk';
```

{% endtab %}
{% endtabs %}

## Step 3: Initialize the SDK

Add a target element and call `init()`:

```html
<!-- Where the button should appear -->
<div id="claim-credential"></div>

<script>
    LearnCard.init({
        publishableKey: 'pk_your_key_here',
        target: '#claim-credential',
        credential: { name: 'Your Template Name' },
        partnerName: 'Your Organization Name',
    });
</script>
```

The credential name must match a template you created in the dashboard. The SDK resolves it server-side — you don't need to embed the full credential JSON.

## Step 4: Customize Branding (Optional)

```js
LearnCard.init({
    publishableKey: 'pk_your_key_here',
    target: '#claim-credential',
    credential: { name: 'Course Completion' },
    partnerName: 'Learning Economy Academy',
    branding: {
        primaryColor: '#1F51FF', // Button + stepper color
        accentColor: '#0F3BD9', // Hover states
        partnerLogoUrl: 'https://your-org.com/logo.png',
        walletUrl: 'https://app.learncard.com',
    },
});
```

## Step 5: Handle Success (Optional)

By default, after claiming, the SDK opens the wallet in a new tab (deep-linked to the credential via `handoffUrl`) and shows a success screen. You can hook into this with `onSuccess`:

```js
LearnCard.init({
    publishableKey: 'pk_your_key_here',
    target: '#claim-credential',
    credential: { name: 'Course Completion' },
    onSuccess: ({ credentialId, handoffUrl }) => {
        // Runs in addition to the wallet auto-open
        document.getElementById('success-message').style.display = 'block';
    },
});
```

To suppress the automatic wallet redirect entirely, set `branding.walletUrl: ''`:

````js
LearnCard.init({
  publishableKey: 'pk_your_key_here',
  target: '#claim-credential',
  credential: { name: 'Course Completion' },
  branding: { walletUrl: '' },  // Disable auto-open
  onSuccess: ({ credentialId, handoffUrl }) => {
    // You fully control what happens next
    document.getElementById('success-message').style.display = 'block';
  },
});

## Complete Example

```html
<!DOCTYPE html>
<html>
  <head>
    <title>Course Complete</title>
  </head>
  <body>
    <h1>Congratulations! You finished the course.</h1>
    <p>Claim your credential to add it to your LearnCard wallet.</p>

    <div id="claim-credential"></div>
    <div id="success" style="display:none; color: green;">
      ✅ Credential claimed! Check your LearnCard wallet.
    </div>

    <script src="https://cdn.jsdelivr.net/npm/@learncard/embed-sdk@latest/dist/learncard.js"></script>
    <script>
      LearnCard.init({
        publishableKey: 'pk_your_key_here',
        target: '#claim-credential',
        credential: { name: 'Intro to Digital Credentials — Course' },
        partnerName: 'Learning Economy Academy',
        branding: {
          primaryColor: '#2EC4A5',
          partnerLogoUrl: 'https://your-org.com/logo.png',
        },
        onSuccess: () => {
          document.getElementById('success').style.display = 'block';
        },
      });
    </script>
  </body>
</html>
````

## Claim Flow

```mermaid
sequenceDiagram
    participant User
    participant Modal as Claim Modal
    participant API as LearnCard API
    participant Wallet as User Wallet

    User->>Modal: Clicks Claim Credential
    Note over Modal: Step 1 — Email View
    User->>Modal: Enters email address
    Modal->>API: POST /contact-methods/challenge
    API-->>User: OTP sent to email
    Note over Modal: Step 2 — OTP View
    User->>Modal: Enters 6-digit code
    Modal->>API: POST /contact-methods/verify
    API-->>Modal: Session JWT
    Note over Modal: Step 3 — Accept View
    User->>Modal: Clicks Accept Credential
    Modal->>API: POST /inbox/claim with JWT
    API->>Wallet: Issue and store credential
    Note over Modal: Success — confetti + checkmark
    Modal->>User: View My LearnCard button
```

## Whitelisted Domains

For security, the API only accepts claims from domains you've whitelisted in the Embed Code tab of your dashboard. Add your production domain before going live.

During local development, `localhost` is allowed automatically.

## Testing Locally

Use the included embed example to test without a real backend:

```bash
# From repo root
bun --filter @learncard/embed-sdk run build
cd examples/embed-example && bun run dev
```

| URL                                                             | Mode                                                 |
| --------------------------------------------------------------- | ---------------------------------------------------- |
| `http://localhost:4321`                                         | Stub mode — no backend, flows all the way to success |
| `?pk=pk_xxx`                                                    | Live network                                         |
| `?pk=pk_xxx&template=My+Template`                               | Live network + template by name                      |
| `?pk=pk_xxx&api=http://localhost:4000/api&template=My+Template` | Fully local                                          |

## Troubleshooting

**"This integration could not be found"** Your `publishableKey` doesn't match any active integration on the network. Double-check the key from your dashboard Embed Code tab and ensure your domain is whitelisted.

**Credential not appearing after claim** The credential lands in the user's inbox and is finalized when they next open their wallet. If you need to verify immediately, check the dashboard's activity tab.

**OTP not arriving** In local dev, check your brain-service logs — OTP codes are printed there when no email provider is configured.

## See Also

* [Embed SDK Reference](/sdks/embed-sdk)
* [Embed Code Tab (Dashboard)](/how-to-guides/connect-systems/connect-a-website)
* [Connect an Embedded App](/how-to-guides/connect-systems/connect-an-embedded-app) — for apps inside LearnCard


# Connect an Embedded App

How-To Guide: Issue Credentials from Embedded Apps in the LearnCard App Store

Build apps that run inside LearnCard and issue credentials directly to users. This guide covers the App Store integration for embedded applications that want to award badges, certificates, or other verifiable credentials.

## Overview

The LearnCard App Store allows third-party applications to be embedded within the LearnCard app. These embedded apps can:

* **Authenticate users** via Single Sign-On (SSO)
* **Issue credentials** directly to the user's wallet
* **Request credentials** for verification or gating
* **Request consent** for data sharing agreements and terms acceptance

This is ideal for:

* Learning platforms awarding course completion badges
* Games issuing achievement credentials
* Assessment tools providing certification
* Any app that wants to reward users with verifiable credentials

## Architecture

```
┌─────────────────────────────────────────────────────┐
│                   LearnCard App                      │
│  ┌───────────────────────────────────────────────┐  │
│  │              Your Embedded App                 │  │
│  │                                               │  │
│  │   1. User completes action                    │  │
│  │   2. App calls sendCredential(templateAlias)  │  │
│  │   3. LearnCard issues from your template      │  │
│  │   4. User sees claim modal                    │  │
│  │   5. Credential stored in wallet              │  │
│  │                                               │  │
│  └───────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘
```

## Prerequisites

1. **App Store Listing** - Your app must be registered in the LearnCard App Store
2. **Boost Templates** - Pre-configured credential templates attached to your listing
3. **Partner Connect SDK** - For communication with the LearnCard host

## Quick Start

### Step 1: Install the SDK

{% tabs %}
{% tab title="npm" %}

```bash
npm install @learncard/partner-connect
```

{% endtab %}

{% tab title="Bun" %}

```bash
bun add @learncard/partner-connect
```

{% endtab %}

{% tab title="yarn" %}

```bash
yarn add @learncard/partner-connect
```

{% endtab %}
{% endtabs %}

### Step 2: Initialize and Send a Credential

```typescript
import { createPartnerConnect } from '@learncard/partner-connect';

// Initialize the SDK
const learnCard = createPartnerConnect();

// When user completes an achievement, send a credential
async function awardBadge() {
    try {
        const result = await learnCard.sendCredential({
            templateAlias: 'course-completion', // Your template alias from the App Store
            templateData: {
                // Optional: fill in template variables
                courseName: 'JavaScript 101',
                completionDate: new Date().toISOString(),
            },
        });

        console.log('Credential issued!', result.credentialUri);
    } catch (error) {
        console.error('Failed to issue credential:', error);
    }
}
```

That's it! The user will see a claim modal and can accept the credential into their wallet.

## Setting Up Your App Store Listing

### 1. Create Your App Listing

In the LearnCard Developer Portal, create a new app listing with:

* **Name & Description** - What your app does
* **Launch URL** - Where your embedded app is hosted
* **Permissions** - Request `credentials:write` to issue credentials
* **Age Rating** - Content rating for your app (optional)
* **Minimum Age** - Minimum user age required to access your app (optional)

#### Age Restrictions

You can configure age-based access controls for your app:

| Field        | Type                                   | Description                                                                                                         |
| ------------ | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `age_rating` | `'4+'` \| `'9+'` \| `'12+'` \| `'17+'` | Content rating similar to app store ratings. Indicates the maturity level of content.                               |
| `min_age`    | `number` (0-18)                        | Minimum age (in years) required to access this app. Users below this age will not see or be able to launch the app. |

{% hint style="info" %}
Age restrictions are enforced based on the user's date of birth in their profile
{% endhint %}

{% hint style="warning" %}
**Hard vs soft enforcement**

* **`min_age`** is a hard minimum age requirement. If a user's age is known and below `min_age`, the user is blocked from installing the app (including for managed/child profiles).
* **`age_rating`** is a content rating. For managed/child profiles, installs that would violate the rating will require guardian approval.
* If a managed/child profile's age is **unknown** (no valid DOB on the profile), the install flow will require the guardian to verify age (e.g., by entering DOB) before continuing.
  {% endhint %}

{% hint style="info" %}
**Contract-based listings and managed profiles**

If your listing's launch configuration includes a `contractUri`, the install flow will require **guardian approval** for managed/child profiles before the child can install/consent.
{% endhint %}

### 2. Create Credential Templates

Templates define what credentials your app can issue. For each type of credential:

1. Go to your app listing's **Templates** tab
2. Click **Add Template**
3. Design your credential (name, description, image, achievement type)
4. Note the **Template Alias** (auto-generated from name, e.g., `course-completion`)

The Template Alias is what you'll reference when issuing credentials from your app.

### 3. Configure Signing Authority

When you add a template to your listing, LearnCard automatically configures a signing authority. This allows credentials to be issued on behalf of your app with proper cryptographic signatures.

### 4. App Issuer Identity

App-issued credentials are signed as the app DID, not the integration owner's DID. The format is:

```
did:web:network.learncard.com:app:<slug>
```

In the LearnCard App, credentials issued by apps display the app name and icon (with a link back to the app listing).

## API Reference

### `sendCredential({ templateAlias, templateData? })`

Issue a credential to the current user.

**Parameters:**

| Parameter       | Type     | Required | Description                                       |
| --------------- | -------- | -------- | ------------------------------------------------- |
| `templateAlias` | `string` | Yes      | The template alias configured in your app listing |
| `templateData`  | `object` | No       | Values for template variables (e.g., `{{name}}`)  |

**Returns:** `Promise<TemplateCredentialResponse>`

```typescript
interface TemplateCredentialResponse {
    credentialUri: string; // URI of the issued credential
    boostUri: string; // URI of the boost template used
}
```

**Example with Template Data:**

```typescript
const result = await learnCard.sendCredential({
    templateAlias: 'quiz-master',
    templateData: {
        score: 95,
        quizName: 'Advanced TypeScript',
        attempts: 1,
    },
});
```

### Error Handling

```typescript
try {
    const result = await learnCard.sendCredential({
        templateAlias: 'my-badge',
    });
} catch (error) {
    switch (error.code) {
        case 'LC_UNAUTHENTICATED':
            // User not logged in
            showLoginPrompt();
            break;
        case 'TEMPLATE_NOT_FOUND':
            // templateAlias doesn't exist for this app
            console.error('Invalid template alias');
            break;
        case 'UNAUTHORIZED':
            // App doesn't have permission
            console.error('Missing credentials:write permission');
            break;
        default:
            console.error('Credential issuance failed:', error.message);
    }
}
```

### `initiateTemplateIssue(templateUri)`

Let users send peer-to-peer badges to each other. Unlike `sendCredential` (which issues from your app to the current user), this opens a flow where the user selects a recipient from their contacts.

**Parameters:**

| Parameter     | Type     | Required | Description                           |
| ------------- | -------- | -------- | ------------------------------------- |
| `templateUri` | `string` | Yes      | The URI of the badge template to send |

**Returns:** `Promise<TemplateIssueResponse>`

```typescript
interface TemplateIssueResponse {
    issued: boolean; // Whether the user completed the issuance flow
}
```

**Example:**

```typescript
// Let users send a "Thank You" badge to someone
async function sendThankYouBadge() {
    try {
        const result = await learnCard.initiateTemplateIssue('urn:lc:boost:thank-you-badge');

        if (result.issued) {
            console.log('Badge sent successfully!');
        } else {
            console.log('User cancelled the badge flow');
        }
    } catch (error) {
        console.error('Failed to initiate badge:', error);
    }
}
```

**How it works:**

1. Your app calls `initiateTemplateIssue` with a template URI
2. LearnCard opens a recipient picker for the user
3. User selects someone from their contacts
4. Badge is sent from the user to the recipient

**Setting up peer badge templates:**

1. Go to your app listing's **Templates** tab
2. Create a template and select **Peer Badge** as the type
3. Copy the **Template URI** shown after creation
4. Use this URI when calling `initiateTemplateIssue`

{% hint style="info" %}
Peer badges are sent **from the user** to another person, not from your app. This is ideal for recognition, gratitude, or social features within your app.
{% endhint %}

### `requestConsent(contractUri?, options?)`

Request user consent for a ConsentFlow contract. This is useful when your app needs explicit user permission for data sharing, terms acceptance, or other consent-based flows.

{% hint style="info" %}
**App Store Apps with Configured Contracts**

If your app is installed from the LearnCard App Store and has a consent contract configured in its integration settings, you can omit the `contractUri` parameter. The SDK will automatically resolve the configured contract from your listing's integration.

**Before (explicit contract URI):**

```typescript
const result = await learnCard.requestConsent(
    'lc:network:network.learncard.com/trpc:contract:abc123'
);
```

**After (using configured contract):**

```typescript
const result = await learnCard.requestConsent();
```

This simplifies your code and ensures users always consent to the correct contract for your app.
{% endhint %}

**Parameters:**

| Parameter     | Type                    | Required | Description                                                                                       |
| ------------- | ----------------------- | -------- | ------------------------------------------------------------------------------------------------- |
| `contractUri` | `string`                | No       | The URI of the ConsentFlow contract. Can be omitted for App Store apps with configured contracts. |
| `options`     | `RequestConsentOptions` | No       | Additional options for the consent flow                                                           |

**Options:**

| Option     | Type      | Default | Description                                                                 |
| ---------- | --------- | ------- | --------------------------------------------------------------------------- |
| `redirect` | `boolean` | `false` | If `true`, redirects to the contract's configured URL after consent granted |

**Returns:** `Promise<ConsentResponse>`

```typescript
interface ConsentResponse {
    granted: boolean; // Whether the user granted consent
}
```

**Basic Example (App Store app with configured contract):**

```typescript
// Request consent using the contract configured in your listing
const result = await learnCard.requestConsent();

if (result.granted) {
    console.log('User granted consent!');
    // Proceed with data access
} else {
    console.log('User declined consent');
}
```

**With Explicit Contract URI:**

```typescript
// Request consent for a specific data sharing agreement
const result = await learnCard.requestConsent('urn:lc:contract:my-data-agreement');

if (result.granted) {
    console.log('User granted consent!');
} else {
    console.log('User declined consent');
}
```

**With Redirect:**

```typescript
// Request consent and redirect to your callback URL after approval
const result = await learnCard.requestConsent(undefined, {
    redirect: true,
});

// If redirect is true and user consents, they will be redirected
// to the contract's configured redirectUrl with a VP (Verifiable Presentation)
// containing proof of consent
```

**How Redirect Works:**

When `redirect: true` is set and the user grants consent:

1. LearnCard generates a Verifiable Presentation (VP) proving consent
2. User is redirected to the contract's `redirectUrl`
3. The VP and user's DID are appended as URL parameters
4. Your server can verify the VP to confirm consent

The redirect URL will include:

* `did` - The user's DID
* `vp` - A signed Verifiable Presentation (JWT format)

**Use Cases:**

* **Terms of Service** - Require users to accept terms before accessing features
* **Data Sharing Agreements** - Get explicit consent before sharing data with third parties
* **Privacy Policies** - Track user acknowledgment of privacy policies
* **OAuth-like Flows** - Use redirect mode for server-side consent verification

{% hint style="info" %}
If the user has already consented to a contract, calling `requestConsent` will return `{ granted: true }` immediately without showing the consent modal again.
{% endhint %}

## User Experience Flow

When your app issues a credential:

1. **Credential Created** - LearnCard issues the credential using your boost template
2. **Claim Modal Appears** - User sees a preview of the credential
3. **User Accepts** - Credential is stored in their wallet
4. **Notification Updated** - If user dismisses, they can claim later from notifications

```
┌──────────────────────────────────┐
│     🎉 New Credential!           │
│                                  │
│  ┌────────────────────────────┐  │
│  │                            │  │
│  │    [Credential Preview]    │  │
│  │                            │  │
│  └────────────────────────────┘  │
│                                  │
│  JavaScript 101 Completion       │
│  By YourApp                      │
│                                  │
│  ┌────────────────────────────┐  │
│  │     Accept Credential      │  │
│  └────────────────────────────┘  │
│                                  │
│         [Dismiss]                │
└──────────────────────────────────┘
```

***

## Requesting Learner Context for AI

Embedded apps can request comprehensive learner context to power AI-driven experiences. This retrieves the user's credentials and personal data (with consent) and formats it for AI consumption.

### When to Use Learner Context

* **AI Tutors** - Adapt explanations based on learner's existing skills
* **Personalized Recommendations** - Suggest content based on credential history
* **Smart Assessments** - Adjust difficulty based on demonstrated competencies
* **Learning Pathways** - Build custom paths from existing achievements

### Prerequisites

1. **App Store Installation** - Your app must be installed from the LearnCard App Store
2. **Consent Contract** - Must have a consent contract configured in your listing's integration
3. **User Consent** - User must have consented to share their data

### Basic AI Tutor Integration

```typescript
import { createPartnerConnect } from '@learncard/partner-connect';

const learnCard = createPartnerConnect();

async function initializeAITutor() {
    // Step 1: Ensure user has consented (uses configured contract)
    const consent = await learnCard.requestConsent();

    if (!consent.granted) {
        showMessage('Please consent to share your learning data for personalization');
        return;
    }

    // Step 2: Request learner context
    const context = await learnCard.requestLearnerContext({
        includeCredentials: true, // Include user's credentials
        includePersonalData: true, // Include name, bio, etc.
        format: 'prompt', // Get LLM-ready text
        detailLevel: 'expanded', // Detailed information
        instructions: 'Focus on technical skills and certifications',
    });

    // Step 3: Use context in your AI system prompt
    const systemPrompt = `You are a helpful tutor assisting ${context.displayName || 'a learner'}.

${context.prompt}

Adapt your teaching style and recommendations based on the learner's background above.`;

    // Step 4: Send to your AI service
    const aiResponse = await fetch('/api/ai-tutor', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            systemPrompt,
            userMessage: 'How do I learn advanced TypeScript?',
        }),
    });

    const result = await aiResponse.json();
    displayAIResponse(result.message);
}
```

### Using Structured Data

For custom AI integrations or when you need direct access to credentials:

```typescript
async function analyzeLearnerSkills() {
    // Request structured data instead of prompt
    const context = await learnCard.requestLearnerContext({
        includeCredentials: true,
        includePersonalData: false,
        format: 'structured', // Returns raw data
    });

    // Access credentials directly
    const credentials = context.raw?.credentials || [];

    // Build custom skill analysis
    const skillCategories = credentials.reduce((acc, cred) => {
        const category = cred.achievementType || 'other';
        acc[category] = (acc[category] || 0) + 1;
        return acc;
    }, {});

    // Generate custom prompt
    const customPrompt = `Learner has ${credentials.length} credentials:
${Object.entries(skillCategories)
    .map(([cat, count]) => `- ${cat}: ${count}`)
    .join('\n')}`;

    return customPrompt;
}
```

### Error Handling

```typescript
try {
    const context = await learnCard.requestLearnerContext({
        includeCredentials: true,
        format: 'prompt',
    });

    // Use context in AI system
} catch (error) {
    switch (error.code) {
        case 'LC_UNAUTHENTICATED':
            showLoginPrompt('Please log in to LearnCard');
            break;
        case 'USER_REJECTED':
            showMessage('User declined to share their learning data');
            break;
        case 'UNAUTHORIZED':
            showMessage('App not properly configured. Please check your App Store listing.');
            break;
        default:
            console.error('Failed to get learner context:', error.message);
    }
}
```

### Configuration Checklist

To use learner context in your app:

1. **Create a Consent Contract** in LearnCard that defines what data your app needs
2. **Configure the Contract** in your App Store listing's integration settings
3. **Request Consent** using `requestConsent()` (without contractUri)
4. **Request Context** using `requestLearnerContext()` after consent is granted

{% hint style="info" %}
The learner context feature respects user privacy. Users must explicitly consent to share their data, and they can revoke consent at any time through their LearnCard settings.
{% endhint %}

## Recording AI Sessions

After using learner context to personalize an AI tutoring experience, you can record the session to create a structured learning history. AI Sessions appear in the user's AI Topics page, building a portfolio of their AI-assisted learning.

### When to Record Sessions

* **After AI Tutoring** - Document what was learned during the session
* **Skill Demonstrations** - Record when the user shows competency
* **Learning Milestones** - Mark significant learning achievements
* **Progress Tracking** - Build a history of learning interactions

### Session Structure

AI Sessions are organized hierarchically:

```
AI Topic (Your App)
├── Session 1: Introduction to TypeScript
├── Session 2: Advanced Types
├── Session 3: Generics and Utility Types
└── ...
```

The AI Topic is automatically created on your app's first session. All subsequent sessions are organized under this same topic.

### Basic Session Recording

```typescript
async function recordLearningSession(sessionData) {
    // Ensure user has consented
    const consent = await learnCard.requestConsent();
    if (!consent.granted) {
        showMessage('Please consent to store your learning progress');
        return;
    }

    try {
        const session = await learnCard.sendAiSessionCredential({
            sessionTitle: sessionData.title,
            summaryData: {
                title: sessionData.title,
                summary: sessionData.summary,
                learned: sessionData.takeaways,
                skills: sessionData.skills.map(s => ({
                    title: s.name,
                    description: s.description,
                })),
                nextSteps: sessionData.recommendations.map(r => ({
                    title: r.title,
                    description: r.description,
                    // keywords is optional — omit unless you have taxonomy data
                })),
                reflections: sessionData.reflections.map(r => ({
                    title: r.question,
                    description: r.answer,
                })),
            },
            metadata: {
                duration: sessionData.durationSeconds,
                difficulty: sessionData.difficulty,
                topics: sessionData.topics,
            },
        });

        console.log('Session recorded:', session.sessionCredentialUri);
        console.log('Topic:', session.topicUri);

        // 🎉 Celebrate the user's first AI session ever from this app
        if (session.isNewTopic) {
            showCelebration('First AI session recorded!');
        }
    } catch (error) {
        console.error('Failed to record session:', error);
    }
}
```

### Complete AI Tutor Example

Here's a complete example combining learner context and session recording:

```typescript
import { createPartnerConnect } from '@learncard/partner-connect';

const learnCard = createPartnerConnect();

class AITutor {
    async initialize() {
        // Step 1: Get learner context for personalization
        const consent = await learnCard.requestConsent();
        if (!consent.granted) {
            throw new Error('User consent required');
        }

        const context = await learnCard.requestLearnerContext({
            includeCredentials: true,
            format: 'prompt',
            instructions: 'Focus on technical skills and learning gaps',
        });

        // Use context to personalize AI
        this.systemPrompt = `You are a tutor. ${context.prompt}`;
        this.userDid = context.did;

        return context;
    }

    async conductSession(userQuestion) {
        // Get AI response using personalized context
        const aiResponse = await this.getAIResponse(userQuestion);

        // Display to user and collect feedback
        const sessionData = await this.runInteractiveSession(aiResponse);

        // Record the session
        await this.recordSession(sessionData);

        return sessionData;
    }

    async recordSession(data) {
        const session = await learnCard.sendAiSessionCredential({
            sessionTitle: data.title,
            summaryData: {
                title: data.title,
                summary: data.summary,
                learned: data.takeaways,
                skills: data.demonstratedSkills.map(s => ({
                    title: s.name,
                    description: s.description,
                })),
                nextSteps: data.recommendations,
                reflections: data.userReflections,
            },
        });

        return session;
    }
}

// Usage
const tutor = new AITutor();
await tutor.initialize();
await tutor.conductSession('How do I learn advanced TypeScript?');
```

### Session Data Best Practices

**`title`:**

* Short, scannable headline for the session
* Reuse across `sessionTitle` if you have nothing fancier

**`summary`:**

* One paragraph capturing what happened in the session
* Written so the user (and the AI) can re-orient at a glance

**`learned` (string array):**

* 3–5 main concepts gained, one per bullet
* Use clear, concise language
* Connect to practical applications

**`skills` (array of `{ title, description }`):**

* Group related competencies under a category title
* Description explains what the user can now do
* Use standard skill taxonomy when possible

**`nextSteps` (array of `{ title, description, keywords? }`):**

* Recommend specific follow-up activities
* Vary types (courses, practice, assessments)
* `keywords` is **optional** — only include it if you actually have taxonomy data; otherwise omit the field entirely

**`reflections` (array of `{ title, description }`):**

* Capture learner insights and "aha moments"
* Record self-assessments

### 🎉 Use `isNewTopic` for first-run UX

`sendAiSessionCredential` returns an `isNewTopic: boolean`. The first call from your app creates the AI Topic; subsequent calls reuse it. Use this flag to celebrate first-run, onboard the user, or show a different UI:

```typescript
const session = await learnCard.sendAiSessionCredential(/* ... */);

if (session.isNewTopic) {
    // First time the user has used this app's AI Tutor.
    // Celebrate, show a one-time tour, or send a welcome notification.
    showFirstSessionCelebration();
} else {
    showQuickConfirmation();
}
```

### End-to-End AI Tutor Tutorial

For a complete, runnable AI Tutor walkthrough that wires `requestConsent → requestLearnerContext → sendAiSessionCredential → sendNotification` together, see the working example app at [`examples/app-store-apps/4-request-learner-context-app`](https://github.com/learningeconomy/LearnCard/tree/main/examples/app-store-apps/4-request-learner-context-app/README.md). It demonstrates:

* Resolving the consent contract automatically from your App Store listing
* Caching `requestLearnerContext` (it takes 2–5s in production — cache, render, revalidate)
* Mapping AI tutor outputs to the real `summaryData` schema
* Bridging notifications back into the wallet via the `actionPath` + `launchFeature` pattern

## Tracking App State with Counters

Many embedded apps need to remember small bits of per-user state across sessions — *"how many lessons has this user completed?"*, *"what's their current streak?"*, *"have they hit the threshold to unlock a credential yet?"*. The Partner Connect SDK provides a lightweight counter API for exactly this, scoped to **(user, app)** pairs and stored in the user's LearnCard account.

### When to use counters

* **Progress tracking** — lessons completed, quizzes passed, sessions recorded
* **Streaks** — daily-use streaks, consecutive-correct streaks
* **Threshold-gated credential issuance** — "issue the badge after 10 sessions"
* **Lightweight feature flags** — "has this user seen the onboarding tour?" (use `0` / `1`)

### When **not** to use counters

* **Anything not an integer** — values must be whole numbers. Pre-aggregate fractional state.
* **High-cardinality keys** — there's a hard cap of **50 distinct keys per (user, app)**. If you'd need a counter per lesson ID, consolidate instead (e.g. one `lessons_completed` counter, plus a separate credential or `sendAiSessionCredential` call to record specifics).
* **High-frequency writes** — there's a **100 writes/minute per (user, app)** rate limit. Don't increment a counter on every keystroke; debounce or batch on the client and increment once per meaningful event.
* **Cross-user state** — counters are scoped per-user. There is no shared/global counter.
* **Sensitive or large data** — counters store integers only.

### Limits (load-bearing — design around these)

| Limit                          | Value                                       |
| ------------------------------ | ------------------------------------------- |
| Distinct keys per user-app     | **50**                                      |
| Writes per minute per user-app | **100**                                     |
| Value type                     | Signed integer only                         |
| Key character set              | `^[a-zA-Z0-9_-]+$` (alphanumeric, `_`, `-`) |
| Key length                     | 1–64 characters                             |
| Batch read (`getCounters`)     | Up to 50 keys per call                      |

### API

The SDK exposes three methods. All take **positional arguments** (not options objects).

```typescript
// Increment (or decrement with a negative amount)
incrementCounter(key: string, amount: number): Promise<IncrementCounterResponse>;

// Read a single counter
getCounter(key: string): Promise<GetCounterResponse>;

// Read multiple counters in one round-trip; omit `keys` to read all
getCounters(keys?: string[]): Promise<GetCountersResponse>;
```

**Response shapes:**

```typescript
interface IncrementCounterResponse {
    key: string;
    previousValue: number;
    newValue: number;
}

interface GetCounterResponse {
    key: string;
    value: number;
    updatedAt: string | null; // ISO 8601, or null if the counter has never been set
}

interface GetCountersResponse {
    counters: GetCounterResponse[];
}
```

A counter that has never been incremented reads as `value: 0, updatedAt: null` from `getCounter`. You don't need to "create" a counter — the first `incrementCounter` call brings it into existence atomically.

{% hint style="warning" %}
**Asymmetry to be aware of:** `getCounter('foo')` returns `{ key: 'foo', value: 0, updatedAt: null }` for keys that have never been set. `getCounters(['foo', 'bar'])` only returns entries for keys that **do** exist — missing keys are simply absent from the `counters` array. Use `??` defaults when reading via `getCounters`, as shown in the dashboard example below.
{% endhint %}

### Example: simple progress tracking

```typescript
import { createPartnerConnect } from '@learncard/partner-connect';

const learnCard = createPartnerConnect();

async function onLessonComplete(lessonId: string) {
    // Atomic increment — no read-modify-write race condition
    const { newValue } = await learnCard.incrementCounter('lessons_completed', 1);
    showProgress(`You've completed ${newValue} lessons!`);
}

async function loadDashboard() {
    const { counters } = await learnCard.getCounters([
        'lessons_completed',
        'quizzes_passed',
        'streak_days',
    ]);

    const lookup = Object.fromEntries(counters.map(c => [c.key, c.value]));
    renderDashboard({
        lessons: lookup.lessons_completed ?? 0,
        quizzes: lookup.quizzes_passed ?? 0,
        streak: lookup.streak_days ?? 0,
    });
}
```

### Example: threshold-gated credential issuance

A common pattern is "issue a badge once the user crosses a threshold". Because `incrementCounter` returns the **new value** atomically, you can gate issuance on the response without a separate read:

```typescript
async function recordSessionAndMaybeAwardBadge() {
    // Record the session credential first
    await learnCard.sendAiSessionCredential({
        /* ... */
    });

    // Then bump the counter and check the threshold in one call
    const { previousValue, newValue } = await learnCard.incrementCounter('sessions_completed', 1);

    // Use the *previous* value to make this idempotent: only issue when
    // we just crossed the threshold, not every call after it.
    if (previousValue < 10 && newValue >= 10) {
        await learnCard.sendCredential({
            templateAlias: 'ten-session-badge',
        });
        await learnCard.sendNotification({
            title: '🎉 10 sessions complete!',
            body: 'You earned the Persistent Learner badge.',
            actionPath: '/badges',
            priority: 'high',
        });
    }
}
```

{% hint style="info" %}
**Why use `previousValue < threshold && newValue >= threshold`?** This guarantees the badge is issued exactly once even if the user hits the threshold, you call again, and the counter has already moved past it. It's the idempotent way to react to "the moment of crossing".
{% endhint %}

### Example: daily streaks

```typescript
async function recordDailyVisit() {
    const { value: lastVisitDay } = await learnCard.getCounter('last_visit_day');
    const today = Math.floor(Date.now() / 86_400_000); // days since epoch

    if (lastVisitDay === today) return; // already counted today

    await learnCard.incrementCounter('last_visit_day', today - lastVisitDay);

    if (lastVisitDay === today - 1) {
        // Visited yesterday — extend the streak
        await learnCard.incrementCounter('streak_days', 1);
    } else if (lastVisitDay !== 0) {
        // Missed at least one day — reset
        const { value } = await learnCard.getCounter('streak_days');
        await learnCard.incrementCounter('streak_days', -value + 1);
    } else {
        // First-ever visit
        await learnCard.incrementCounter('streak_days', 1);
    }
}
```

This is intentionally illustrative — for serious streak logic you'd record the date as part of an AI Session credential, not as a counter. Counters shine for simple monotonic counts.

### Error handling

```typescript
try {
    await learnCard.incrementCounter('sessions_completed', 1);
} catch (err) {
    if (err instanceof PartnerConnectError) {
        switch (err.code) {
            case 'LC_UNAUTHENTICATED':
                // User signed out mid-session
                showLoginPrompt();
                break;
            case 'BAD_REQUEST':
                // Most common causes:
                //   - invalid key (failed the regex or length check)
                //   - > 50 distinct keys for this user-app
                //   - rate limit exceeded (> 100 writes/min)
                console.error('Counter rejected:', err.message);
                break;
            case 'UNAUTHORIZED':
                // App is missing the counters permission in its listing
                console.error('App not allowed to use counters');
                break;
            default:
                console.error(err);
        }
    }
}
```

### Best practices

* **Pick stable, well-named keys.** Treat counter keys like database column names — once your app is in production, renaming a key effectively zeroes out everyone's history.
* **Consolidate aggressively.** Prefer one `lessons_completed` counter over `lesson_1_complete`, `lesson_2_complete`, … (the 50-key cap is unforgiving).
* **Pair counters with credentials, don't replace them.** Counters are for cheap aggregate state. The actual record of what the user did should still be a credential (`sendCredential`, `sendAiSessionCredential`).
* **Debounce writes on the client.** If a user can tap a button 30 times in 5 seconds, increment once with `amount: 30` instead of calling 30 times.
* **Use `incrementCounter`'s return value** rather than read-then-write — it's atomic, so you avoid race conditions and save a round-trip.

## Complete Example

Here's a full example of a simple embedded app:

```html
<!DOCTYPE html>
<html>
    <head>
        <title>Quiz App</title>
    </head>
    <body>
        <div id="quiz">
            <h1>JavaScript Quiz</h1>
            <button id="complete">Complete Quiz</button>
        </div>

        <script type="module">
            import { createPartnerConnect } from '@learncard/partner-connect';

            const learnCard = createPartnerConnect();

            // Get user identity for personalization
            async function init() {
                try {
                    const identity = await learnCard.requestIdentity();
                    console.log('User:', identity.user.did);
                } catch (e) {
                    if (e.code === 'LC_UNAUTHENTICATED') {
                        // Handle unauthenticated state
                    }
                }
            }

            // Award badge when quiz is completed
            document.getElementById('complete').addEventListener('click', async () => {
                try {
                    const result = await learnCard.sendCredential({
                        templateAlias: 'quiz-completion',
                        templateData: {
                            quizName: 'JavaScript Fundamentals',
                            score: 92,
                            completedAt: new Date().toISOString(),
                        },
                    });

                    alert('Badge awarded! Check your wallet.');
                } catch (error) {
                    alert('Failed to award badge: ' + error.message);
                }
            });

            init();
        </script>
    </body>
</html>
```

## Best Practices

### 1. Design Meaningful Credentials

* Use clear, descriptive names
* Include relevant achievement details
* Add appealing images/icons
* Set appropriate achievement types (Badge, Certificate, etc.)

### 2. Issue at the Right Moment

* Award credentials immediately after achievement
* Don't spam users with too many credentials
* Consider combining small achievements into milestone badges

### 3. Handle Errors Gracefully

* Always wrap credential issuance in try/catch
* Provide feedback to users on success/failure
* Log errors for debugging

### 4. Test Thoroughly

* Use the App Preview feature in the Developer Portal
* Test with different user states (logged in, logged out)
* Verify credentials appear correctly in the wallet

## Testing Your Integration

### Using the Developer Portal Preview

1. Go to your app listing in the Developer Portal
2. Click **Preview App**
3. Your app loads in a test iframe
4. Test credential issuance - credentials go to your wallet

### Local Development

For local development, use the Developer Portal's **Preview App** feature which provides a test iframe environment. Your app will be loaded within LearnCard and credentials will be issued to your test wallet.

## Related Documentation

* [Partner Connect SDK](/sdks/partner-connect) - Full SDK reference
* [Boost Credentials](/core-concepts/credentials-and-data/boost-credentials) - Understanding boosts
* [Connect a Website](/how-to-guides/connect-systems/connect-a-website) - Alternative: server-side issuance via ConsentFlow
* [Connect a Game](/how-to-guides/connect-systems/connect-a-game) - Game-specific integration patterns


# Connect a Game

How-To Guid: Connect Your Educational Game with LearnCard GameFlow

Want to enable your educational game to securely issue verifiable badges, track learning progress, and respect user privacy with robust guardian consent? This guide will walk you through integrating your game with LearnCard using **GameFlow**.

## **Our Example Use Case: "Math Adventures" Game**

Imagine "Math Adventures," an online game designed to teach elementary school students fundamental math concepts. As students complete levels or master specific skills (e.g., "Addition Master," "Fraction Whiz"), Math Adventures wants to:

1. Allow parents/guardians to connect their child's LearnCard account.
2. Securely issue digital badges for these achievements directly to the child's LearnCard wallet, with guardian oversight.
3. Optionally, track in-game learning progress using xAPI statements linked to the student's LearnCard identity.

{% hint style="info" %}
GameFlow is a specialized framework built on LearnCard's powerful **ConsentFlow** system. It's designed to create a seamless and secure bridge between educational games and a student's LearnCard digital wallet. [Learn more.](/core-concepts/consent-and-permissions/gameflow-overview)
{% endhint %}

## **What you'll accomplish in this Guide:**

1. Set up your game's backend as a LearnCard Issuer.
2. Create a "GameFlow Contract" with specific settings for guardian consent and data permissions.
3. Implement a "Connect with LearnCard" flow in your game for players/guardians.
4. Handle the consent callback to securely link a player's game account with their LearnCard DID (and capture delegate credentials for xAPI).
5. Automatically issue a "Level Completion" badge when a player achieves a milestone in your game.

## Prerequisites

* **LearnCard SDK Initialized (for your Game's Backend):** Your game's backend server will need an active `learnCard` instance.
* **Service Profile for Your Game:** Your game will need its own identity on the LearnCard Network.
* **Basic Understanding:** Familiarity with [DIDs](/core-concepts/identities-and-keys/decentralized-identifiers-dids), [Verifiable Credentials (VCs),](/core-concepts/credentials-and-data/verifiable-credentials-vcs) [Boosts](/core-concepts/credentials-and-data/boost-credentials), and the [general ConsentFlow Tutorial ](/tutorials/create-a-consentflow)is highly recommended. Understanding [xAPI Concepts ](/tutorials/sending-xapi-statements)will be useful if you plan to use that feature.
* **Web Development Setup:**
  * A backend environment for your game (e.g., Node.js with Express).
  * A frontend for your game (HTML/JS, or your game engine's web interface).

## Part 1: Setting Up Your Game's Backend as an Issuer

Similar to other services, your game's backend needs a LearnCard identity to issue credentials.

### **Step 1.1: Initialize LearnCard SDK on Your Backend**

```typescript
// backend/game-learncard-setup.ts
import { initLearnCard } from '@learncard/init';
// import didkit from '@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm'; 

// IMPORTANT: Store your seed securely (e.g., environment variable in production)
const MATH_ADVENTURES_ISSUER_SEED = 'your-math-adventures-game-secure-hex-seed'; // Replace!

let gameLearnCardInstance; 

export async function getGameLearnCard() {
    if (gameLearnCardInstance) return gameLearnCardInstance;

    console.log('Initializing Math Adventures LearnCard SDK...');
    gameLearnCardInstance = await initLearnCard({
        seed: MATH_ADVENTURES_ISSUER_SEED,
        network: true,
        allowRemoteContexts: true,
        // didkit: didkit, 
    });
    console.log("Math Adventures LearnCard Initialized.");
    console.log("Math Adventures Issuer DID:", gameLearnCardInstance.id.did());
    return gameLearnCardInstance;
}

```

### **Step 1.2: Ensure Your Game Has a Service Profile**

```typescript
// backend/game-profile-manager.ts
import { getGameLearnCard } from './game-learncard-setup';

const GAME_PROFILE_ID = 'math-adventures-game';
const GAME_DISPLAY_NAME = 'Math Adventures Game';

export async function ensureGameProfile() {
    const learnCard = await getGameLearnCard();
    try {
        let profile = await learnCard.invoke.getProfile(GAME_PROFILE_ID);
        if (!profile) {
            console.log(`Creating service profile for Math Adventures: ${GAME_PROFILE_ID}`);
            await learnCard.invoke.createServiceProfile({
                profileId: GAME_PROFILE_ID,
                displayName: GAME_DISPLAY_NAME,
                image: 'https://example.com/math-adventures-logo.png' // Optional
            });
            console.log('Math Adventures Service Profile created successfully.');
        } else {
            console.log('Math Adventures Service Profile already exists.');
        }
    } catch (error: any) {
        // Handle potential errors, e.g., if profileId is already taken by someone else
        if (error.message?.includes('Profile ID already exists')) {
            console.log('Math Adventures Service Profile already exists (confirmed by error).');
        } else {
            console.error(`Failed to ensure Math Adventures profile: ${error.message}`);
        }
    }
}

// Call this during your game server's startup sequence
// ensureGameProfile();

```

{% hint style="success" %}
**Action:** Integrate this setup into your game server's initialization process.
{% endhint %}

## Part 2: Creating the GameFlow Contract

A GameFlow Contract is a specialized ConsentFlow Contract. It tells LearnCard what permissions your game needs and, crucially, enables guardian consent features.

### **Step 2.1: Define Your GameFlow Contract Terms**

```typescript
// backend/gameflow-contract.ts
import { getGameLearnCard } from './game-learncard-setup';

// IMPORTANT: This URL must be an endpoint on YOUR game's domain that can handle the redirect
const YOUR_GAME_CONSENT_CALLBACK_URL = 'https://mathadventures.example.com/learncard/callback';

const mathAdventuresGameFlowContract = {
    name: "Math Adventures - Connect & Save Progress",
    subtitle: "Unlock digital badges for your math achievements!",
    description: "Connect LearnCard to save your child's game progress and earn verifiable badges for mastering math skills. Guardian consent is required.",
    image: "https://example.com/math-adventures-contract.png", // Optional
    
    // --- GameFlow Specific Settings ---
    needsGuardianConsent: true, // This is KEY for GameFlow!
    redirectUrl: YOUR_GAME_CONSENT_CALLBACK_URL,
    reasonForAccessing: "Math Adventures uses LearnCard to save your child's learning progress, track achievements, and issue digital badges for completed levels and mastered skills. This helps create a verifiable record of their learning journey.",
    // --- End GameFlow Specific Settings ---

    contract: {
        read: { // What Math Adventures might want to read (e.g., existing math badges)
            credentials: {
                categories: {
                    "Achievement": { required: false } // e.g., to see if they already have certain math badges
                }
            }
        },
        write: { // What Math Adventures wants to write (issue)
            credentials: {
                categories: {
                    "Achievement": { required: true } // For issuing level completion, skill mastery badges
                }
            }
            // Optionally, if your game assigns unique IDs to players recognized by LearnCard
            // personal: { "MathAdventuresPlayerID": { required: false } } 
        }
    }
};

let gameFlowContractUriCache: string | null = null;

export async function getOrCreateGameFlowContractUri(): Promise<string> {
    if (gameFlowContractUriCache) return gameFlowContractUriCache;

    const learnCard = await getGameLearnCard();
    try {
        console.log('Creating Math Adventures GameFlow Contract...');
        const uri = await learnCard.invoke.createContract(mathAdventuresGameFlowContract);
        console.log('GameFlow Contract Created! URI:', uri);
        gameFlowContractUriCache = uri;
        return uri;
    } catch (error) {
        console.error('Error creating GameFlow contract:', error);
        throw error;
    }
}

```

**Key GameFlow Configurations:**

* `needsGuardianConsent: true`: Activates the guardian consent flow. The LearnCard App will guide an adult to verify their presence, select the child playing, and then grant consent.
* `redirectUrl`: Where the user (or guardian) is sent after the consent process. It will receive the consenting user's DID (which could be the child's or guardian's, depending on the flow) and potentially a Verifiable Presentation (`vp`) containing a Delegate Credential for xAPI.
* `reasonForAccessing`: A parent-friendly message explaining why your game needs access.

{% hint style="success" %}
**Action:** Have your game server create this contract (e.g., on startup or via an admin function) and securely store the returned `gameFlowContractUriCache`.
{% endhint %}

## Part 3: Frontend - "Connect with LearnCard" in Your Game

In your game's UI (e.g., settings menu, start screen, or when a significant achievement is reached for the first time), provide a button for players/guardians to connect their LearnCard.

```html
<button id="connectLearnCardGameBtn">Connect LearnCard & Save Progress</button>

<script>
    // This script would run in your game's frontend
    const connectGameButton = document.getElementById('connectLearnCardGameBtn');

    connectGameButton.addEventListener('click', async () => {
        try {
            // Fetch this URI from your game server
            const gameFlowContractUri = 'uri:contract:YOUR_GAMEFLOW_CONTRACT_URI'; // Replace!
            
            // Where the user should land in your game after the consent flow
            const gameReturnUrl = 'https://mathadventures.example.com/game/profile?learncard_setup=complete'; 

            if (gameFlowContractUri === 'uri:contract:YOUR_GAMEFLOW_CONTRACT_URI') {
                alert('Error: GameFlow Contract URI not configured. Please contact game admin.');
                return;
            }
            
            const consentUrl = `https://learncard.app/consent-flow?uri=${encodeURIComponent(gameFlowContractUri)}&returnTo=${encodeURIComponent(gameReturnUrl)}`;
            
            console.log('Redirecting to LearnCard for GameFlow consent:', consentUrl);
            // In a web game, this redirects the main window.
            // For native games using a webview, you'd load this URL in the webview.
            window.location.href = consentUrl; 
        } catch (error) {
            console.error('Error preparing GameFlow consent URL:', error);
            alert('Could not initiate LearnCard connection for the game. Please try again.');
        }
    });
</script>

```

{% hint style="success" %}
**Action:** Implement this button. It redirects to `learncard.app` for the GameFlow consent process (which includes guardian steps).
{% endhint %}

## Part 4: Handling the GameFlow Callback

After the consent process on `learncard.app` (which might involve guardian verification and child selection), the user is redirected back to your game's `redirectUrl` (e.g., `https://mathadventures.example.com/learncard/callback`).

This callback will include query parameters:

* `did`: The DID of the consenting user (this might be the child's DID if selected by a guardian, or the guardian's DID if they are consenting for themselves or if the child selection step wasn't needed/used).
* `vp` (Optional): A Verifiable Presentation (as a JWT string) containing a "Delegate Credential." This is crucial if you plan to send xAPI statements on behalf of the user.

### **Step 4.1: Backend - Create a Callback Endpoint**

```typescript
// backend/routes/gameAuthRoutes.ts (example using Express.js)
// import express from 'express';
// const gameAuthRouter = express.Router();

// This is your endpoint: e.g., GET https://mathadventures.example.com/learncard/callback
// gameAuthRouter.get('/learncard/callback', async (req, res) => {
async function handleGameFlowCallback(req, res) { // req, res are Express request/response
    const userDid = req.query.did as string;
    const delegateVpJwt = req.query.vp as string; // JWT string for the Verifiable Presentation

    console.log(`GameFlow callback received for User DID: ${userDid}`);
    if (delegateVpJwt) {
        console.log(`Received Delegate Credential VP (JWT - first 50 chars): ${delegateVpJwt.substring(0,50)}...`);
        // TODO: You should verify this VP and extract the Delegate Credential.
        // This credential allows your game server to make xAPI calls on behalf of userDid.
        // Store this delegateVpJwt or the extracted credential securely, associated with userDid.
    }

    if (userDid) {
        // TODO:
        // 1. Get your game's internal player ID (e.g., from the current session if the user was logged into your game)
        const internalPlayerId = req.session?.playerId; // Example

        if (!internalPlayerId) {
            console.error('Game player session not found during GameFlow callback.');
            // res.status(400).send('Player session not found. Please log in to Math Adventures first.');
            return; 
        }

        // 2. Securely store the userDid (and delegateVpJwt if applicable) in your game's database, 
        //    associating it with the internalPlayerId.
        //    Example: await gameDb.players.update({ id: internalPlayerId }, { learnCardDid: userDid, delegateVp: delegateVpJwt });
        console.log(`ACTION: Store LearnCard DID ${userDid} for game player ${internalPlayerId}`);
        if (delegateVpJwt) {
            console.log(`ACTION: Store Delegate VP JWT for game player ${internalPlayerId} to enable xAPI statements.`);
        }
        
        // 3. Redirect the user back into your game, perhaps to a success page or their profile.
        //    The `returnTo` parameter used in Part 3 would typically handle this final redirect.
        //    If not using `returnTo`, then redirect from here:
        //    res.redirect('https://mathadventures.example.com/game/profile?learncard_connected=true');
        console.log('Redirecting player back into the game...');
    } else {
        console.error('GameFlow callback error: DID not found in query parameters.');
        // res.status(400).send('LearnCard GameFlow connection failed or was denied.');
    }
}
// export default gameAuthRouter;

```

{% hint style="success" %}
**Action:** Implement this callback endpoint. Securely store the `userDid` (and `delegateVpJwt`) associated with the player in your game.
{% endhint %}

## Part 5: Issuing a Game Achievement Badge

When a player completes a level or masters a skill in Math Adventures:

### **Step 5.1: Create a Boost for Your Game Badge (One-time Setup)**

Similar to the "Issue Badges from a Website" tutorial, create a Boost for each type of badge (e.g., "Addition Level 1 Complete").

```typescript
// backend/game-badge-manager.ts
import { getGameLearnCard } from './game-learncard-setup';
import { randomUUID } from 'crypto';

const ADDITION_L1_BOOST_NAME = 'Math Adventures: Addition Level 1 Badge';
let additionL1BoostUri: string | null = null;

export async function getOrCreateAdditionL1Boost(): Promise<string> {
    if (additionL1BoostUri) return additionL1BoostUri;
    const learnCard = await getGameLearnCard();
    const badgeTemplate = { /* ... Define your badge VC structure ... */ 
        "@context": ["https://www.w3.org/2018/credentials/v1", "https://purl.imsglobal.org/spec/ob/v3p0/context.json", "https://ctx.learncard.com/boosts/1.0.3.json"],
        type: ["VerifiableCredential", "OpenBadgeCredential", "BoostCredential"],
        name: ADDITION_L1_BOOST_NAME,
        credentialSubject: {
            achievement: {
                achievementType: "Badge",
                name: "Addition Level 1 Mastered",
                description: "Awarded for successfully completing all challenges in Addition Level 1 of Math Adventures.",
                id: "urn:uuid:" + randomUUID(),
                image: "https://example.com/badges/math-addition-l1.png",
                type: ["Achievement"],
                criteria: { narrative: "Player demonstrated proficiency in single-digit addition."}
            },
            type: ["AchievementSubject"]
        },
        display: { backgroundColor: "#4CAF50", displayType: "badge" },
        image: "https://example.com/badges/math-addition-l1.png"
    };
    const boostMetadata = { name: ADDITION_L1_BOOST_NAME, description: 'Awards a badge for completing Addition Level 1.', category: 'Achievement' };
    try {
        console.log('Creating Addition Level 1 Boost...');
        additionL1BoostUri = await learnCard.invoke.createBoost(badgeTemplate, boostMetadata);
        console.log('Addition Level 1 Boost Created! URI:', additionL1BoostUri);
        return additionL1BoostUri;
    } catch (error) { /* ... error handling ... */ throw error; }
}
// getOrCreateAdditionL1Boost(); // Call on server startup

```

{% hint style="success" %}
**Action:** Create Boosts for all your game's achievements. Store their URIs.
{% endhint %}

### **Step 5.2: Backend Logic - Issue Badge on Achievement**

```typescript
// backend/game-achievements.ts
import { getGameLearnCard } from './game-learncard-setup';
import { getOrCreateGameFlowContractUri } from './gameflow-contract';
import { getOrCreateAdditionL1Boost } from './game-badge-manager';
import { randomUUID } from 'crypto';

export async function awardAdditionLevel1Badge(internalPlayerId: string, playerName: string) {
    const learnCard = await getGameLearnCard();
    
    // 1. Retrieve the player's LearnCard DID (stored in Part 4)
    // const playerLearnCardDid = await gameDb.players.getLearnCardDid(internalPlayerId); // Example
    const playerLearnCardDid = 'PLAYER_DID_FROM_YOUR_DB'; // Replace with actual lookup

    if (!playerLearnCardDid) {
        console.log(`Player ${internalPlayerId} has not connected LearnCard. Cannot issue badge.`);
        return;
    }

    try {
        const gameContractUri = await getOrCreateGameFlowContractUri();
        const boostUriToIssue = await getOrCreateAdditionL1Boost();

        const credentialForPlayer = {
            "@context": ["https://www.w3.org/2018/credentials/v1", "https://purl.imsglobal.org/spec/ob/v3p0/context.json", "https://ctx.learncard.com/boosts/1.0.3.json"],
            id: "urn:uuid:" + randomUUID(),
            type: ["VerifiableCredential", "OpenBadgeCredential", "BoostCredential", "AchievementCredential"],
            issuer: learnCard.id.did(), // Game's DID
            issuanceDate: new Date().toISOString(),
            name: `Math Adventures: Addition Level 1 - ${playerName}`,
            credentialSubject: {
                id: playerLearnCardDid, // The player's DID
                achievement: {
                    achievementType: "Badge",
                    name: "Addition Level 1 Mastered",
                    description: `Awarded to ${playerName} for mastering Addition Level 1.`,
                }
            }
        };
        
        console.log(`Issuing Addition L1 badge to ${playerLearnCardDid} via contract ${gameContractUri}`);
        const issuedCredentialUri = await learnCard.invoke.writeCredentialToContract(
            playerLearnCardDid,
            gameContractUri,
            credentialForPlayer,
            boostUriToIssue
        );
        console.log(`Addition L1 Badge successfully issued to ${playerLearnCardDid}! Credential URI: ${issuedCredentialUri}`);
    } catch (error) {
        console.error(`Error issuing Addition L1 badge to player ${internalPlayerId}:`, error);
    }
}

// Example: When player completes level 1
// awardAdditionLevel1Badge('game_player_789', 'Player One');

```

{% hint style="success" %}
**Action:** Integrate this logic into your game's achievement system.
{% endhint %}

## Part 6: (Optional) Sending xAPI Statements

If you captured the `delegateVpJwt` in Part 4, your game server can now send xAPI statements on behalf of the user.

```typescript
// backend/xapi-handler.ts

// Assume userDid and delegateVpJwt were stored for the player
// const userDid = 'PLAYER_DID_FROM_YOUR_DB'; e.g. `did:web:network.learncard.com:users:${profileId}`
// const delegateVpJwt = 'JWT_STRING_CAPTURED_FROM_CALLBACK'; 
// const xapiEndpoint = 'https://cloud.learncard.com/xapi/statements';

async function sendGameXAPIStatement(playerDid: string, playerDelegateVpJwt: string, verbId: string, verbDisplay: string, activityId: string, activityName: string, activityDesc: string) {
    const statement = {
        actor: {
            objectType: "Agent",
            name: playerDid,
            account: { homePage: "https://www.w3.org/TR/did-core/", name: playerDid }
        },
        verb: { id: verbId, display: { "en-US": verbDisplay } },
        object: {
            id: activityId,
            definition: { name: { "en-US": activityName }, description: { "en-US": activityDesc }, type: "http://adlnet.gov/expapi/activities/serious-game" }
        }
    };

    try {
        const response = await fetch(xapiEndpoint, { // Ensure xapiEndpoint is defined
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-Experience-API-Version': '1.0.3',
                'X-VP': playerDelegateVpJwt // Use the Delegate VP JWT for authentication
            },
            body: JSON.stringify(statement)
        });
        if (!response.ok) { /* ... error handling ... */ }
        const responseData = await response.json();
        console.log('xAPI statement for game activity sent successfully:', responseData);
    } catch (error) { /* ... error handling ... */ }
}

// Example: Player started a new puzzle
// sendGameXAPIStatement(
//     userDid, 
//     delegateVpJwt,
//     "http://adlnet.gov/expapi/verbs/attempted", "attempted",
//     "https://mathadventures.example.com/puzzles/addition_5", "Addition Puzzle 5", "Attempted puzzle 5 in Addition Level 1."
// );

```

This allows rich tracking of in-game learning, authenticated as the user via the delegate credential.

## Summary & Next Steps

You've now learned how to integrate your educational game with LearnCard using GameFlow! This includes: ✅ Setting up your game as an Issuer. ✅ Creating a GameFlow Contract with guardian consent. ✅ Enabling players/guardians to connect their LearnCard accounts. ✅ Handling the callback to link game accounts with LearnCard DIDs (and capture delegate VPs for xAPI). ✅ Automatically issuing achievement badges to players via the GameFlow contract. ✅ (Optionally) Sending authenticated xAPI statements.

This robust integration enhances your game by providing verifiable recognition for players and valuable, secure data tracking capabilities, all while prioritizing user control and safety.

Explore further:

* Dive deeper into the [ConsentFlow Core Concepts](/core-concepts/consent-and-permissions/consentflow-overview) that power [GameFlow](/core-concepts/consent-and-permissions/gameflow-overview).
* Learn more about [xAPI](/core-concepts/credentials-and-data/xapi-data) and its integration with LearnCard.
* Design various [**Boosts**](/core-concepts/credentials-and-data/boost-credentials) for all the unique achievements and skills in your game.


# Connect AI Agent

How to connect your AI client to LearnCard's MCP server

**Purpose**

Connect your AI assistant (e.g. Claude or ChatGPT) to LearnCard to enable personalized learning experiences. Your AI will be able to access your learning context, ask questions about your background, and save learning session summaries as verifiable credentials.

**LearnCard MCP Server URL (for reference):**

```
https://mcp.learncard.ai/
```

Below are instructions for connecting the LearnCard MCP server with Claude or ChatGPT.

***

## Claude

#### Claude **Pro or higher required**.

{% embed url="<https://www.loom.com/share/a18c5bc1bd8349c5a80d898ecd149eb9>" %}

<details>

<summary>Screenshots of steps</summary>

#### **Steps:**

1. Go to [https://claude.ai](https://claude.ai/) and sign in.
2. Click your profile picture (bottom-left).

<figure><img src="/files/DYK81R3wz3C0QlW0pfnz" alt=""><figcaption></figcaption></figure>

3. Click `Settings`

<figure><img src="/files/ldRfGBiswp3XJvWlR9CS" alt=""><figcaption></figcaption></figure>

4. Click `Connectors`

<figure><img src="/files/CezmLCX3bxIKvcUuuQ5W" alt=""><figcaption></figcaption></figure>

5. Click `Add custom connector`

<figure><img src="/files/8D7vxHWm9Y7fbLBmK3JN" alt=""><figcaption></figcaption></figure>

6. Enter:

* **Name:** `LearnCard`
* **Remote MCP Server URL:**

  ```
  https://mcp.learncard.ai/
  ```

<figure><img src="/files/liQAewpkqGn18sSgAGeM" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/d6drv22rYHml10A4bgF2" alt=""><figcaption></figcaption></figure>

7. Click `Add`

<figure><img src="/files/V4wIjGGZiOrZpahum74p" alt=""><figcaption></figcaption></figure>

8. Click `Connect` next to the LearnCard custom connector

<figure><img src="/files/mggbrSDyy5HHcDka5OUc" alt=""><figcaption></figcaption></figure>

9. You should be re-directed to learncard.app. Sign in and click `Continue as <Your Name>`

<figure><img src="/files/wTkJBvUb2SHpNczx8H7m" alt=""><figcaption></figcaption></figure>

10. You should be redirected to claude.ai and see a confirmation. The connector will now show `Configure`.

<figure><img src="/files/0gHSWkpkqDKm1FPcEs8s" alt=""><figcaption></figcaption></figure>

11. Now, to confirm Claude has access to the LearnCard connector during live chats, start a new chat with Claude and click this button:

<figure><img src="/files/pY5pFI9l3YxHd8fjqpEu" alt=""><figcaption></figcaption></figure>

12. Make sure the LearnCard connector is enabled. It should be toggled on as shown in the picture below.

<figure><img src="/files/GVlMFWzLCNtzoRpdd7Hh" alt=""><figcaption></figcaption></figure>

13. Now, start a learning session with Claude. If it asks for permission to call the tools provided by the LearnCard connector, it’s working! Allow Claude to use the LearnCard connector and continue your AI tutoring session.

<figure><img src="/files/X1rCnVRO5VpfKTIeS6Mg" alt=""><figcaption></figcaption></figure>

</details>

***

## ChatGPT

#### ChatGPT Plus **or higher required**.

{% embed url="<https://www.loom.com/share/d90d6fbb70c54514a0704f73735818cc>" %}

<details>

<summary>Screenshots of steps</summary>

**Steps:**

1. Go to <https://chatgpt.com/> and sign in.
2. Click your profile picture (bottom-left)

<figure><img src="/files/YcNZeDXz655NnqVkprgg" alt=""><figcaption></figcaption></figure>

3. Click `Settings`

<figure><img src="/files/RlQXhf0xvycAJCZBJiHT" alt=""><figcaption></figcaption></figure>

4. Click `Apps & Connectors`

<figure><img src="/files/jogFznYgGrq3SOicewk8" alt=""><figcaption></figcaption></figure>

5. Scroll down

<figure><img src="/files/KsjdpNEaYNVUbcHEct0J" alt=""><figcaption></figcaption></figure>

6. Click `Advanced settings`

<figure><img src="/files/VYNqo00TlEMnbnd73Iut" alt=""><figcaption></figcaption></figure>

7. Toggle on `Developer mode`

<figure><img src="/files/DOjq16S7XCq8Ftc6UoIB" alt=""><figcaption></figcaption></figure>

8. Click `Back`

<figure><img src="/files/GGVUwxPgKIbPbGEMQbky" alt=""><figcaption></figcaption></figure>

9. Click `Create app`

<figure><img src="/files/RZVtuH9eJPLpGQRIo0zm" alt=""><figcaption></figcaption></figure>

10. Enter:

* **Name:** `LearnCard`
* **MCP Server URL:**

  ```
  https://mcp.learncard.ai/
  ```
* Check `I understand and want to continue`

<figure><img src="/files/izRU8uIOEyxVd8Jfzmq2" alt=""><figcaption></figcaption></figure>

11. Click `Create`

<figure><img src="/files/H3J3MiylXhjFsgBg4W8T" alt=""><figcaption></figcaption></figure>

12. Sign in to learncard.app if prompted, then click `Continue as <Your Name>`

<figure><img src="/files/wTkJBvUb2SHpNczx8H7m" alt=""><figcaption></figcaption></figure>

13. You should be redirected back to chatgpt.com and see a confirmation. The connector will now show a red `Disconnect` button.

<figure><img src="/files/sS7diwnre6byiC857rKc" alt=""><figcaption></figcaption></figure>

14. Start a new chat with ChatGPT and click the `+` button

<figure><img src="/files/rvIDkgcyCtbBqrYZqDvP" alt=""><figcaption></figcaption></figure>

15. Click `More` and select `LearnCard` .

    <figure><img src="/files/WXKumM9jln3Y0fmfuns0" alt=""><figcaption></figcaption></figure>
16. ChatGPT should now show LearnCard attached to the chat input. When you send a message, ChatGPT will request permission to use the connector — that means it’s working! Approve access to continue your AI tutoring session.

<figure><img src="/files/ZwZe1sQADouQOxRTSwOl" alt=""><figcaption></figcaption></figure>

</details>


# Implement Flows


# Claim Data after Guardian Consent

How to implement the Consentful "Claim Later" Flow

### Overview

The Consentful "Claim Later" Flow enables applications to save an ephemeral user's session data as a "Save Game" state, which can be restored after obtaining guardian consent. This pattern is ideal for applications that need to:

* Create low-PII profiles quickly (using nicknames)
* Allow users to start using the application immediately
* Obtain guardian consent after initial engagement
* Restore the user's session state after consent is granted

### Use Case: MyLittleTabbyCat

To illustrate this flow, we'll use a fictional application called "MyLittleTabbyCat" where a child creates a personalized tabby cat and receives daily inspirational quotes. The child initially creates their cat in an ephemeral session, then takes a QR code home for their guardian to scan and provide consent.

### Implementation Steps

{% hint style="warning" %}
Before you begin, make sure you've [setup a Service Profile ](https://docs.learncard.com/learn-card-sdk/learncard-network/learncard-network-api/profile#id-2.-create-a-service-profile)in the network for your LearnCard.

```javascript
const serviceProfile = {
    displayName: 'My Tabby Cat',
    profileId: 'my-tabby-cat',
    image: 'https://i.postimg.cc/s2xdx5Ss/erik-jan-leusink-Ib-Px-GLg-Ji-MI-unsplash.jpg',
};

await learnCard.invoke.createServiceProfile(serviceProfile);
```

{% endhint %}

#### 1. Create a "Save Game" Boost

After the user creates their content in your application, store this data as a Boost on the LearnCard network:

{% hint style="info" %}
This example shows how to extend a regular "BoostCredential" with a "TabbyCat" schema in JSON-LD. Check it out on the JSON-LD playground [here](https://learning-economy-foundation.gitbook.io/ler-documentation/). You can verify it's a valid credential by "issuing it" in the CLI:

```javascript
await learnCard.invoke.issueCredential(credential);
```

{% endhint %}

<pre class="language-javascript"><code class="lang-javascript">// Prepare the credential with user data
const credential {
  "@context": [
    "https://www.w3.org/2018/credentials/v1",
    "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.1.json",
    "https://ctx.learncard.com/boosts/1.0.3.json",
    {
      "tabby": "https://docs.mylittletabbycat.com/definitions#",
      "xsd": "https://www.w3.org/2001/XMLSchema#",
      "TabbyCat": {
        "@context": {
          "catName": {
            "@id": "tabby:catName",
            "@type": "xsd:string"
          },
          "bodyParts": {
            "@container": "@set",
            "@context": {
              "head": {
                "@id": "tabby:partsHead",
                "@type": "xsd:string"
              },
              "body": {
                "@id": "tabby:partsBody",
                "@type": "xsd:string"
              },
              "ears": {
                "@id": "tabby:partsEars",
                "@type": "xsd:string"
              }
            },
            "@id": "tabby:bodyParts"
          },
          "externalUserId": {
            "@id": "tabby:externalUserId",
            "@type": "xsd:string"
          }
          
        },
        "@id": "tabby:tabbyCat"
      },
    }
  ],
  "credentialSubject": {
    "achievement": {
      "achievementType": "ext:LCA_CUSTOM:Social Badge:TabbyCat",
      "criteria": {
        "narrative": "[Nickname] created a unique tabby cat."
      },
      "description": "Personalized tabby cat.",
      "id": "urn:uuid:123",
      "image": "https://i.postimg.cc/s2xdx5Ss/erik-jan-leusink-Ib-Px-GLg-Ji-MI-unsplash.jpg",
      "name": "Tabby Cat Completion",
      "type": [
        "Achievement"
      ]
    },
    "id": "did:web:network.learncard.com:users:jsmith",
    "type": [
      "AchievementSubject"
    ]
  },
  "display": {
    "backgroundColor": "",
    "backgroundImage": "",
    "displayType": "badge"
  },
  "image": "https://i.postimg.cc/s2xdx5Ss/erik-jan-leusink-Ib-Px-GLg-Ji-MI-unsplash.jpg",
  "skills": [],
  "issuanceDate": "2025-04-01T16:56:00.667Z",
  "issuer": "did:web:network.learncard.com:users:my-tabby-cat",
  "name": "Tabby Cat",
  "catName": "Butternut",
  "bodyParts": {
    "head": "ROUND",
    "body": "STRIPED",
    "ears": "FLOPPY"
  },
  "externalUserId": "12345",
  "type": [
    "VerifiableCredential",
    "OpenBadgeCredential",
    "BoostCredential",
    "TabbyCat"
  ]
};

// Add searchable metadata for later lookup
const metadata = {
  name: 'Butternut',
  description: 'User data for MyLittleTabbyCat application, for cat "Butternut."',
};

// Create the boost
<strong>const boostUri = await learnCard.invoke.createBoost(credential, metadata);
</strong></code></pre>

#### 2. Create a Guardian ConsentFlow

After creating the boost, set up a ConsentFlow that requires guardian consent:

{% hint style="warning" %}
To attach autoboosts to your ConsentFlow, you need to setup a "Signing Authority". You can do this once for your LearnCard service profile like so:<br>

```javascript
// Make sure to bun add @learncard/lca-api-plugin
import { getLCAPlugin } from '@learncard/lca-api-plugin';

// Add the LCA API plugin to your LearnCard
const lcaApiLearnCard = await learnCard.addPlugin(
    await getLCAPlugin(learnCard, 'https://api.learncard.app/trpc')
);

// Create a Signing Authority (one-time). The name is an arbitrary identifier.
const sa = await lcaApiLearnCard.invoke.createSigningAuthority('autoboost');

if (!sa) throw new Error('Could not create signing authority.');

// Register a signing authority with LearnCard Network
await lcaApiLearnCard.invoke.registerSigningAuthority(sa.endpoint, sa.name, sa.did);

// Get the signing authority
const saResult = await lcaApiLearnCard.invoke.getRegisteredSigningAuthority(sa.endpoint, sa.name);

if (!saResult) throw new Error('Could not register signing authority.');

// Save this — use when creating contracts with autoboosts
const signingAuthority = {
    endpoint: saResult.signingAuthority.endpoint,
    name: saResult.relationship.name,
};
```

{% endhint %}

```javascript
// Create a GameFlow contract (based on ConsentFlow)
const gameFlowContract = {
    name: 'MyLittleTabbyCat Game',
    subtitle: "Guardian Consent for Child's Tabby Cat",
    description: 'Allow your child to save their tabby cat and receive inspirational quotes',
    image: 'https://i.postimg.cc/s2xdx5Ss/erik-jan-leusink-Ib-Px-GLg-Ji-MI-unsplash.jpg',
    needsGuardianConsent: true,

    // Ensure this redirects to a valid URL where the user will go after consent
    redirectUrl: 'https://mylittletabbycat.com/callback',
    reasonForAccessing: 'Allow your child to save their progress and tabby cat design',
    contract: {
        read: {
            anonymize: true,
            credentials: {
                categories: {
                    'Social Badge': { required: false },
                },
            },
        },
        write: {
            credentials: {
                categories: {
                    'Social Badge': { required: true },
                },
            },
        },
    },
    // Most important part: link the boost for auto-claim after consent
    autoboosts: [
        {
            boostUri,
            signingAuthority,
        },
    ],
};

// Create the contract on the LearnCard network
const contractUri = await learnCard.invoke.createContract(gameFlowContract);

// Generate QR code URL for the contract
const consentLink = `https://learncard.app/consent-flow?uri=${contractUri}`;
```

#### 3. Generate QR Code for User

Display a QR code linking to the ConsentFlow for the user:

```javascript
// Generate QR code (example using a hypothetical QR library)
const qrCode = generateQRCode(consentLink);

// Display to user with instructions
displayQRCode(qrCode, 'Have your guardian scan this code to save your cat!');
```

#### 4. Handle Redirect After Guardian Consent

After the guardian provides consent, LearnCard redirects to your application's callback URL with the user's DID:

```javascript
// Example callback handler (server-side route)
app.get('/callback', async (req, res) => {
    const userDid = req.query.did;

    if (!userDid) {
        return res.redirect('/error?message=No+user+ID+provided');
    }

    // Store the LearnCard DID in your system, associated with the user account
    await storeUserDid(userDid);

    // Redirect to the restoration page
    res.redirect(`/restore?did=${userDid}`);
});
```

#### 5. Retrieve "Save Game" Data

After receiving the user's DID, retrieve their boost data to restore their session:

```javascript
// Client-side restoration code
async function restoreUserSessionFromProfileID(userProfileId) {
  try {

    if userProfileId.contains("did") {
     // If profileID is in "did" format (i.e. "did:web:network.learncard.com:users:my-tabby-cat"), extract plain profileId
      userProfileId = userProfileId.split(':').reverse()[0]
    }

    const sentCredentials = await learnCard.invoke.getSentCredentials(userProfileId)

    if (sentCredentials.length <= 0) {
      showError("Guardian has not consented for this profile ID.")
    }

    const catCredential = await learnCard.read.get(sentCredentials[0].uri)

    if (catCredential) {
      restoreUserSession(catCredential)
    } else {
      showError("Could not find your saved cat");
    }

  } catch (error) {
    console.error("Error restoring session:", error);
    showError("Error restoring your saved game");
  }
}

async function restoreUserSession(catCredential) {
  try {
    if (catCredential) {
      // Extract the saved data
      const catName = catCredential.catName;
      const bodyParts = catCredential.bodyParts;
      const externalUserId = catCredential.externalUserId;

      // Restore the user's experience
      restoreTabbycat(catName, bodyParts, externalUserId);
      showWelcomeBack(catName);
    } else {
      showError("Could not find your saved cat");
    }
  } catch (error) {
    console.error("Error restoring session:", error);
    showError("Error restoring your saved game");
  }
}
```

#### 6. Return User Flow (Login by Cat Name)

For returning users who know their cat's name:

```javascript
async function lookupByCatName(catName) {
    try {
        // Search for boosts with the provided catName in metadata
        const tabbyCatBoosts = await learnCard.invoke.getPaginatedBoosts({
            query: {
                name: catName,
            },
        });

        if (boosts.records.length === 0) {
            return showError('No cat found with that name');
        }

        const boost = boosts.records[0];

        // Check if the boost has been claimed (has recipients) and therefore guardian consent achieved.
        const recipients = await learnCard.invoke.getPaginatedBoostRecipients(boost.uri);

        if (recipients.records.length === 0) {
            return showError(
                "Your cat exists but hasn't been claimed yet. Please ask a guardian to scan the QR code"
            );
        }

        // The boost is claimed, extract user profileId
        const profileId = recipients.records[0].to.profileId;
        const profileDID = `did:web:network.learncard.com:users:${profileId}`;

        // Check that the Guardian approved with consent
        const consentFlowData = await learnCard.invoke.getConsentFlowDataForDid(profileDID);
        const didGuardianConsent = await learnCard.invoke.verifyConsent(
            consentFlowData.records[0].contractUri,
            profileDID
        );

        if (!didGuardianConsent) {
            return showError('Guardian has not yet consented for this user.');
        }

        // Extract the saved cat credential in the boost.
        const catCredential = (await learnCard.invoke.getBoost(boost.uri)).boost;

        // Restore session using the cat credential
        restoreUserSession(catCredential);
    } catch (error) {
        console.error('Error looking up cat:', error);
        showError('Error looking up your cat');
    }
}
```

### Complete Flow Diagram

1. User creates content in your application (ephemeral session)
2. Application creates a Boost with the user's data
3. Application creates a ConsentFlow with guardian consent requirement, linking the Boost
4. User receives a QR code to take home
5. Guardian scans QR code and provides consent
6. User is redirected back to your application with their LearnCard DID
7. Application retrieves "Save Game" data using the DID
8. Application restores the user's session

### Best Practices

1. **Add Searchable Metadata**: Always include unique identifiers in the boost metadata for easy lookup.
2. **Clear Consent Reasons**: Provide clear explanations for guardians about what data is being stored.
3. **Graceful Fallbacks**: Handle cases where consent is denied or the process is interrupted.
4. **Secure User Associations**: Verify that the returning user is associated with the correct boost.
5. **Data Minimization**: Store only necessary information in the boost credential.

### Related Documentation

For more information on GameFlow (the framework this flow is built on), see the [GameFlow ](/core-concepts/consent-and-permissions/gameflow-overview)documentation.


# Guardian-Gated Credentials

How-To Guide: Sending credentials that require guardian approval before a minor can claim them

This guide explains how to issue credentials that require guardian (parent) approval before the recipient can claim them. This is designed for organizations issuing credentials to minors or managed accounts where a trusted adult must consent before the credential is finalized.

## Overview

When you send a credential with a `guardianEmail`, the system:

1. **Emails the guardian** an approval link with credential details
2. **Emails the student** a notice that their credential is awaiting guardian approval
3. **Guardian approves or rejects** via a 6-digit OTP verification flow
4. **Student is notified** of the outcome and can claim the credential if approved

If the guardian already has a LearnCard account with a MANAGES relationship to the student, they can also approve directly in the app without OTP.

## Sending a Guardian-Gated Credential

Add `guardianEmail` to the `options` when calling `send`:

```typescript
const result = await learnCard.invoke.send({
    type: 'boost',
    recipient: 'student@school.edu',
    templateUri: 'urn:lc:boost:abc123',
    options: {
        guardianEmail: 'parent@example.com',
        branding: {
            issuerName: 'Springfield Elementary',
        },
    },
});

console.log(result.inbox?.guardianStatus); // 'AWAITING_GUARDIAN'
console.log(result.inbox?.issuanceId);     // Tracking ID
```

### REST API

```bash
POST /send
Authorization: Bearer <token>
Content-Type: application/json

{
    "type": "boost",
    "recipient": "student@school.edu",
    "templateUri": "urn:lc:boost:abc123",
    "options": {
        "guardianEmail": "parent@example.com"
    }
}
```

### Requirements

* `guardianEmail` must be different from the recipient email
* The credential is held in `AWAITING_GUARDIAN` status until the guardian acts
* The student cannot claim the credential until the guardian approves

## Guardian Approval Flow

The guardian receives an email with a link to the approval page. The flow is:

1. Guardian clicks the approval link
2. System shows credential details (issuer, credential name)
3. Guardian requests a 6-digit OTP (sent to their email)
4. Guardian enters OTP and approves or rejects
5. Student receives an email notification with the outcome

### After Approval

* The credential status changes to `GUARDIAN_APPROVED` and becomes claimable
* If the guardian creates a LearnCard account afterward, the system automatically establishes a **MANAGES** relationship with the student
* Future credentials to that student are automatically guardian-gated (no `guardianEmail` needed from the issuer)

### After Rejection

* The credential status changes to `GUARDIAN_REJECTED`
* The student is notified but cannot claim the credential

## Automatic Guardian Gating (Managed Accounts)

Once a MANAGES relationship exists between a guardian and a student, **all future inbox credentials** sent to that student are automatically guardian-gated — even without the issuer specifying `guardianEmail`.

* The guardian receives an in-app notification for each new credential
* The guardian can approve or reject directly in the LearnCard app (no OTP needed)
* The `finalize` response includes a `guardianPending` count showing how many credentials are awaiting approval

```typescript
const finalizeResult = await learnCard.invoke.finalizeInboxCredentials();
console.log(finalizeResult.guardianPending); // Number of credentials awaiting guardian approval
```

## Guardian Status Values

| Status              | Meaning                                                    |
| ------------------- | ---------------------------------------------------------- |
| `AWAITING_GUARDIAN` | Credential sent, waiting for guardian to approve or reject |
| `GUARDIAN_APPROVED` | Guardian approved, student can claim                       |
| `GUARDIAN_REJECTED` | Guardian rejected, student cannot claim                    |

## Related

* [Send Credentials](/how-to-guides/send-credentials) — General credential sending guide
* [Universal Inbox](/core-concepts/network-and-interactions/universal-inbox) — How the inbox system works
* [Network Profiles](/core-concepts/identities-and-keys/network-profiles) — Profile types including managed profiles


# Connect via CHAPI

Sending and Receiving Credentials across interoperable Wallets

## What is CHAPI?

[CHAPI](https://chapi.io/) (**C**redential **H**andler **API**) is an open source protocol that allows conforming wallets to easily send and receive Credentials and Presentations to each other.

> CHAPI is an open protocol designed to solve the “NASCAR Problem” - too often, users are presented with a fixed set of options for authentication with third-party sites. The CHAPI protocol provides mediation between any CHAPI-enabled web application and a third-party site. Just register your web app with your browser, and off you go!

## Using CHAPI

LearnCard packages up and re-exposes the basic CHAPI methods in an attempt to make it as easy as possible to use LearnCard under the hood for a CHAPI compliant application. To learn how to use LearnCard to become CHAPI compliant, click one of the following links!

{% content-ref url="/pages/9OUXr96cFl5PkSW71flf" %}
[CHAPI Wallet Setup Guide](/how-to-guides/implement-flows/chapi/chapi-wallet-setup-guide)
{% endcontent-ref %}

{% content-ref url="/pages/zk9ScZY7S9mndeLVUMZt" %}
[Demo Application](/how-to-guides/implement-flows/chapi/demo-application)
{% endcontent-ref %}

{% content-ref url="/pages/VMHxIMHGxFJWs5ovijes" %}
[Using LearnCard to Interact with a CHAPI Wallet](/how-to-guides/implement-flows/chapi/using-learncard-to-interact-with-a-chapi-wallet)
{% endcontent-ref %}


# CHAPI Wallet Setup Guide

Learn how to use LearnCard to build a CHAPI compliant wallet application

In order to make a CHAPI compliant wallet, there are six things that your site will need to do:

* Install/run the web-credential-polyfill
* Run the `installHandler` method
* Host a public manifest.json file
* Host a public wallet service worker
* \[Optional] Host a storage endpoint for users to visit when storing a credential via CHAPI
* \[Optional] Host a get endpoint for users to visit when retrieving a credential via CHAPI

## Install/run the web-credential-polyfill

Installing and running the `web-credential-polyfill` could not be easier with LearnCard! It is automatically run for you when constructing a wallet!

```typescript
const learnCard = await initLearnCard();
```

## Run the installHandler method

After initializing a wallet, prompt the user to use your application as a CHAPI wallet by calling the `installChapiHandler` method:

```typescript
await learnCard.invoke.installChapiHandler();
```

## Host a public manifest.json file

This step is a *bit* trickier, and is deeply intertwined with the next step. The simple answer here is to add a `manifest.json` file to your site that is hosted at `/manifest.json` with contents similar to the following:

{% code title="manifest.json" %}

```json
{
  "name": "LearnCard Demo CHAPI Wallet",
  "short_name": "LearnCard Demo CHAPI Wallet",
  "icons": [
    {
      "sizes": "64x64",
      "src": "icon.png",
      "type": "image/png"
    }

  ],
  "credential_handler": {
    "url": "/wallet-worker",
    "enabledTypes": ["VerifiablePresentation"]
  }
}
```

{% endcode %}

Replacing the `url` with the path to the service worker you set up in the next step, and pointing `src` to an image file that you would like to appear in the CHAPI menu.

In most bundlers/webapp setups, you will simply place this file inside the `public` directory. However, if you are not using a bundler and instead just hosting static files, you will want to place this file right next to your `index.html` file.

## Host a public wallet service worker

Using the `url` defined in the `manifest.json` above, add a public endpoint to your site that can be used to instantiate an empty wallet and run the following code:

```typescript
import { initLearnCard } from '@learncard/init';

const learnCard = await initLearnCard();

try {
    // This will ask the user if they'd like to use your application as a CHAPI
    // compliant wallet
    await learnCard.invoke.installChapiHandler();
} catch (error) {
    console.error('Error installing Chapi Handler:', error);
}

learnCard.invoke.activateChapiHandler({
    get: async () => {
        // Return an arbitrary route to display to users when requesting a credential
        // or using DIDAuth with your application
        return { type: 'redirect', url: `${window.location.origin}/get` };
    },
    store: async () => {
        // Return an arbitrary route to display to users when storing a credential
        // with your application
        return { type: 'redirect', url: `${window.location.origin}/store` };
    },
});
```

There's a lot going on in this short amount of code, so let's break it down step-by-step:

1. A user visits your site and does something which ultimately instantiates a wallet and calls `installHandler`
2. A pop-up appears, asking the user if they'd like to use your site as a CHAPI wallet. Let's assume they say yes!
3. CHAPI looks at the hosted `manifest.json` to get some basic information about your wallet, such as what it's called and where to find this service worker
4. The same user then uses a website that asks to store a credential using CHAPI
5. A pop-up appears, asking the user what CHAPI wallet they'd like to use. Let's assume they say yours!
6. The `store` function passed into `activateChapiHandler` is called, and the result is used to determine what to display to the user. In the above example, we have specified that we would like the `/store` route to be displayed.
7. The user is shown your site's `/store` route via an iframe.

As you might have been able to tell, the operative code here is the function passed into `activateChapiHandler`. This is what determines what will be shown to users when asking to store a credential.

{% hint style="info" %}
**Hint:** We have arbitrarily chosen the `/store` route here. You can happily use whatever route you'd like to display the storage page to users!
{% endhint %}

## \[Optional] Host a storage endpoint for users to visit when storing a credential via CHAPI

{% hint style="info" %}
**Hint:** If you'd like, you can instead return the same data directly inside the function passed into `activateChapiHandler` to completely skip having users see this storage endpoint
{% endhint %}

After the above flow finishes, a user will land on your sites `/store` route. In order to actually display and store the sent credential, you will need to call `wallet.receiveChapiEvent`:

```typescript
const event = await learnCard.invoke.receiveChapiEvent();

const vp = event.credential.data;

const vc = Array.isArray(vp.verifiableCredential)
    ? vp.verifiableCredential[0]
    : vp.verifiableCredential;
```

After displaying the credential to the user, you may prompt the user for a title and store it with the following code:

```typescript
const uri = await learnCard.store.Ceramic.upload(vc);
await learnCard.index.IDX.add({ id, uri });
```

Once the credential is stored, you may inform the calling code that you have successfully stored the credential with the following code:

```typescript
event.respontWith(Promise.resolve({ dataType: 'VerifiablePresentation', data: vp }););
```

If you would instead prefer to reject the credential, you may do so with the following code:

```typescript
event.respondWith(Promise.resolve(null));
```

## \[Optional] Host a get endpoint for users to visit when retrieving a credential via CHAPI

### DIDAuth

One reason why you might want to have a `get` route setup is for [DID-Auth](https://w3c-ccg.github.io/vp-request-spec/#did-authentication). Including support for DID-Auth allows issuers to seemlessly request your user's did and verify that they actually control that did.

The code to do this looks very similar to the code used for hosting a storage endpoint, however you will want to display different information to the user!

To start, grab the event:

```typescript
const event = await learnCard.invoke.receiveChapiEvent();
```

Next, grab the request origin and display it to the user:

```typescript
const origin = event.credentialRequestOrigin;
```

A good prompt might be "{origin} would like to send you a credential".

If the user accepts, you will need to create a new VP that is signed using the challenge and domain in the DID-Auth request:

```typescript
const presentation = event.credentialRequestOptions.web.VerifiablePresentation;
const { challenge, domain } = presentation;

const didAuthVp = {
    '@context': [
        'https://www.w3.org/2018/credentials/v1',
        'https://w3id.org/security/suites/ed25519-2020/v1',
    ],
    type: 'VerifiablePresentation',
    holder: learnCard.id.did(),
};

const data = await learnCard.invoke.issuePresentation(didAuthVp, {
    challenge,
    domain,
    proofPurpose: 'authentication',
});

event.respondWith(
    Promise.resolve({
        dataType: 'VerifiablePresentation',
        data,
    })
);
```

If the user rejects, simply respond with `null`!

```typescript
event.respondWith(Promise.resolve(null));
```

## Testing

The easiest way to test out your new CHAPI software is by visiting <https://playground.chapi.io/issuer>. Once there, you can easily generate and attempt to store different test credentials into your wallet software.

## Troubleshooting

If you find yourself totally stuck, it can be really helpful to use the [official CHAPI docs](https://chapi.io/developers/wallets) to help get you totally unstuck! Because we are simply wrapping the exposed CHAPI methods, it is very easy to translate the CHAPI docs to the relevant LearnCard functions!

{% content-ref url="/pages/9eC0TpklRIYUCUp5QtJ8" %}
[Translating to CHAPI documentation](/how-to-guides/implement-flows/chapi/translating-to-chapi-documentation)
{% endcontent-ref %}


# Translating to CHAPI documentation

How to translate LearnCard's methods into the CHAPI documentation's methods

## For Wallets

CHAPI exposes four main methods from its two packages:

* `loadOnce` from `credential-handler-polyfill`
* `installHandler` from `web-credential-handler`
* `activeHandler` from `web-credential-handler`
* `receiveCredentialEvent` from `web-credential-handler`

LearnCard exposes three of these methods for you, while implicitly calling `loadOnce` for you at construction time in the CHAPI plugin.

* `learnCard.invoke.installChapiHandler` wraps `installHandler`
* `learnCard.invoke.activateChapiHandler` wraps `activeHandler`
* `learnCard.invoke.receiveChapiEvent` wraps `receiveCredentialEvent`

In the case of `installHandler` and `receiveCredentialEvent`, these two methods are exposed directly, with only types added to help make them easier to use.

When using `activeChapiHandler` instead of `activateHandler`, LearnCard will default the `mediatorOrigin` to `` `https://authn.io/mediator?${encodedURIComponent(window.location.origin)` `` for you. This can be overrided by simply passing in a custom `mediatorOrigin` when calling `activateChapiHandler`.

## For Issuers

The method `storePresentationViaChapi` loosely wraps the `window.navigator.credentials.store` method. However, `learnCard.invoke.storePresentationViaChapi` will automatically convert a raw Verifiable Presentation into the `WebCredential` class for you before calling `window.navigator.credentials.store`.


# Demo Application

Let's Build a CHAPI Wallet App together!

Hey there! 👋 Let's build a CHAPI Wallet App together with LearnCard!

## Pre-Reqs

### Bun

For this example, we will be using [Bun](https://bun.sh/) as a package manager, so let's make sure we have it installed!

```bash
bun --version
```

### Astro

[Astro](https://astro.build/) is a wonderful new Javascript framework that we will be using to build this app. If you haven't heard of or used Astro before, have no fear! It is *very* similar to React, and allows you to import and use React components when necessary, so if you've used React before, you should feel very comfortable using Astro!

### Vite

Under the hood, Astro is using [Vite](https://vitejs.dev/) to bundle and serve files. If you haven't heard of Vite before, I highly recommend checking it out, and definitely consider using it any time you need to make a new website as a replacement for Webpack! We will only need to do a tiny bit of Vite configuration, so have no fear, Vite is only here to make our development process faster!

### ESBuild

Under the hood, Vite is using [ESBuild](https://esbuild.github.io/) to transpile files. This means that ESBuild is ultimately responsible for stripping out TypeScript types, and for converting common js and esm modules back and forth. We won't need to much configuration with ESBuild at all for this site, however, it is definitely important to be aware that it exists under the hood if you find yourself running into issues!

## Boilerplate

### Astro

We will use [Astro](https://astro.build/) to create this app, so let's go ahead and begin!

```bash
bun create astro chapi-example
> Include sample files (recommended)
> Do you plan to write TypeScript? > Yes
> How strict should TypeScript be? > Strict (recommended)
> Install dependencies? (recommended) > Yes
> Initialize a new git repository? (optional) > Yes
```

### React and Tailwind

Great! Now let's `cd chapi-example` and start setting up some boilerplate. To start, let's add support for React and Tailwind

```bash
cd chapi-example
bunx astro add react
> Continue? yes
> Continue? yes
> Continue? yes
bunx astro add tailwind
> Continue? yes
> Continue? yes
> Continue? yes
```

### Aliases

We are going to be importing from a few common directories. To make this easier to do, let's set up some quick TS aliases! Open up the `tsconfig.json` file and add the following:

<pre class="language-json" data-title="tsconfig.json" data-line-numbers><code class="lang-json">{
    "extends": "astro/tsconfigs/strictest",
<strong>    "compilerOptions": {
</strong><strong>        "jsx": "react",
</strong><strong>        "baseUrl": ".",
</strong><strong>        "paths": {
</strong><strong>            "@components/*": ["./src/components/*"],
</strong><strong>            "@helpers/*": ["./src/helpers/*"],
</strong><strong>            "@layouts/*": ["./src/layouts/*"]
</strong><strong>        }
</strong><strong>    }
</strong>}
</code></pre>

### HTTPS in Dev

CHAPI requires us to serve even our dev server over HTTPS, so let's set that up now!

{% hint style="info" %}
**Hint:** This plugin will cause an Insecure warning to appear when visiting our site! This is nothing to worry about, and you may simple click "Proceed anyway" and safely ignore that warning.
{% endhint %}

```bash
bun add --dev @vitejs/plugin-basic-ssl
```

<pre class="language-javascript" data-title="astro.config.mjs" data-line-numbers><code class="lang-javascript">import { defineConfig } from "astro/config";
import react from "@astrojs/react";

import tailwind from "@astrojs/tailwind";

<strong>import basicSsl from '@vitejs/plugin-basic-ssl';
</strong>
// https://astro.build/config
export default defineConfig({
<strong>  vite: {
</strong><strong>    plugins: [basicSsl()],
</strong><strong>  },
</strong>  integrations: [react(), tailwind()],
});
</code></pre>

### Polyfills

[Vite](https://vitejs.dev/) (the bundler that Astro uses under the hood!) has a hard time running `@learncard/core` out of the box due to some deep dependencies relying on the node.js standard library. Fortunately, it is fairly straightforward to polyfill! Let's add that now

```bash
bun add --dev @esbuild-plugins/node-globals-polyfill node-stdlib-browser
```

{% code title="astro.config.mjs" lineNumbers="true" %}

```javascript
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';

import tailwind from '@astrojs/tailwind';

import basicSsl from '@vitejs/plugin-basic-ssl';
import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill';
import stdlibbrowser from 'node-stdlib-browser';

// https://astro.build/config
export default defineConfig({
    vite: {
        plugins: [basicSsl()],
        optimizeDeps: {
            esbuildOptions: {
                define: { global: 'globalThis' },
                plugins: [NodeGlobalsPolyfillPlugin({ process: true, buffer: true })],
            },
        },
        resolve: { alias: stdlibbrowser },
    },
    integrations: [react(), tailwind()],
});
```

{% endcode %}

Great! With all that boilerplate out of the way, we can now *finally* begin the real dev work!

## Landing Page

Let's pop open a terminal and fire up the dev server!

```bash
bun run dev
```

Then, open up a browser and go to `https://localhost:3000` (take great care to make sure you're using https and *not* http!) You should see a screen like this:

{% hint style="info" %}
**Hint:** If you get a warning about the site being insecure, that is okay! You may just click "proceed anyway" and continue your local development.
{% endhint %}

<figure><img src="/files/1npKC0LBil9BJWEebzdq" alt=""><figcaption></figcaption></figure>

Let's remove all this default content and get a basic skeleton app for a simple wallet.

<pre class="language-tsx" data-title="src/pages/index.astro" data-line-numbers><code class="lang-tsx">---
import Layout from "@layouts/Layout.astro";
---

&#x3C;Layout title="Welcome to Astro.">
<strong>  &#x3C;section id="modal-container">&#x3C;/section>
</strong>
  &#x3C;main class="w-full h-full flex flex-col justify-center items-center p-4">
    &#x3C;header>
      &#x3C;h1>LearnCard CHAPI Example&#x3C;/h1>
    &#x3C;/header>

    &#x3C;h3 id="loading-wallet">Loading wallet...&#x3C;/h3>
  &#x3C;/main>
&#x3C;/Layout>
</code></pre>

Change background color in the `Layout.astro` file to white so you can read the dark text.

{% code title="src/layouts/Layout.astro" %}

```tsx
...
html {
    font-family: system-ui, sans-serif;
    background: white;
    background-size: 224px;
}
...
```

{% endcode %}

The site should now look like this:

<figure><img src="/files/M5WBqKKf8gxd79bf5nke" alt=""><figcaption></figcaption></figure>

It's not much, but it's a start! Let's stop that loading text from lying to us and actually add in a wallet! For the purposes of this demo app, let's hardcode the seed `'1234'`. In a real app, you would never want to hardcode the seed used for the wallet, preferring instead to use a truly random source to generate a seed, then securely storing it, but for now, we will be fine simply hardcoding `'1234'`.

To begin, let's install `@learncard/init`, `@learncard/types`, and `@learncard/chapi-plugin`

```bash
bun add @learncard/init @learncard/types @learncard/chapi-plugin
```

Then, we'll instantiate a wallet, and update the UI to reflect that our loading is finished.

<pre class="language-tsx" data-title="src/pages/index.astro" data-line-numbers><code class="lang-tsx">---
import Layout from "@layouts/Layout.astro";
---

&#x3C;Layout title="LearnCard CHAPI Example">
  &#x3C;section id="modal-container">&#x3C;/section>

  &#x3C;main class="w-full h-full flex flex-col justify-center items-center p-4">
    &#x3C;header>
      &#x3C;h1>LearnCard CHAPI Example&#x3C;/h1>
    &#x3C;/header>

    &#x3C;h3 id="loading-wallet">Loading wallet...&#x3C;/h3>
  &#x3C;/main>
&#x3C;/Layout>

<strong>&#x3C;script>
</strong><strong>  import { initLearnCard } from "@learncard/init";
</strong>
<strong>  const learnCard = await initLearnCard({ seed: "1234" });
</strong>
<strong>  const loadingWallet = document.getElementById(
</strong><strong>    "loading-wallet"
</strong><strong>  ) as HTMLElement;
</strong>
<strong>  loadingWallet.innerText = "Wallet loaded!";
</strong><strong>&#x3C;/script>
</strong></code></pre>

This will update our UI to reveal that a wallet has been loaded! Great!

<figure><img src="/files/V0oYgPXi6oMGeaE4gZSh" alt=""><figcaption></figcaption></figure>

Once we've got CHAPI set up and we're able to store credentials in our wallet, we'll come back to this page and display the credentials we've stored, but for now, we can call it a day on this Landing Page! Phew! 😅

## CHAPI

In order to set up CHAPI, we'll need to do a few things:

* Install/run the web-credential-polyfill
* Run the `installHandler` method
* Host a public manifest.json file
* Host a public wallet service worker
* Host a storage endpoint for users to visit when storing a credential via CHAPI

Let's run through those now!

### Install/run the web-credential-polyfill

This is implicitly done for us when calling `initLearnCard`, so we're already done here!

### Run the installHandler method

Our first real step! To do this, simply call the `installChapiHandler` method on the wallet object!

{% code title="src/pages/index.astro" %}

```typescript
const wallet = await initLearnCard({ seed: '1234' });

await wallet.invoke.installChapiHandler();
```

{% endcode %}

### Host a public manifest.json file

This part is a bit tricky. What we need to do is decide on a route to use as a service worker, and then encode that inside of our manifest.json file. Let's use `/wallet-worker`:

{% code title="public/manifest.json" lineNumbers="true" %}

```json
{
    "name": "LearnCard Demo CHAPI Wallet",
    "short_name": "LearnCard Demo CHAPI Wallet",
    "credential_handler": {
        "url": "/wallet-worker",
        "enabledTypes": ["VerifiablePresentation"]
    }
}
```

{% endcode %}

### Host a public wallet service worker

This is where the really interesting bit happens! This service worker will actually be a full route that uses LearnCard to call some special methods. The actual content displayed on this route doesn't matter very much. However, we will need to make an important decision here: What should the user see when being asked to store a credential? In this case, we will redirect them to another route that we will create at `/store.`

{% code title="src/pages/wallet-worker.astro" lineNumbers="true" %}

```tsx
---
import Layout from "@layouts/Layout.astro";
---

<Layout title="wallet-worker">
  <h1>LearnCard CHAPI Example Wallet Worker</h1>

  <h3>You probably shouldn't see this page...</h3>
</Layout>

<script>
  import { initLearnCard } from "@learncard/init";

  const learnCard = await initLearnCard();

  learnCard.invoke.activateChapiHandler({
    store: async () => {
      return { type: "redirect", url: `${window.location.origin}/store` };
    },
  });
</script>
```

{% endcode %}

If you try to visit this page manually, there's not much to see, and you may notice that errors appear in your browser's console. However, when reaching this page via CHAPI, something very important happens:

```typescript
learnCard.invoke.activateChapiHandler({
    store: async () => {
        return { type: 'redirect', url: `${window.location.origin}/store` };
    },
});
```

This small snippet of code tells CHAPI that we'd like to display the `/store` page when a site requests to store a credential with our software. Let's make that `/store` page now!

### Host a storage endpoint for users to visit when storing a credential via CHAPI

We've finally reached our first page that will have enough javascript to justify using React! Let's setup a basic astro page that renders a React component:

{% code title="src/pages/store.astro" lineNumbers="true" %}

```tsx
---
import Layout from '@layouts/Layout.astro';
import CredentialStorage from '@components/CredentialStorage';
---

<Layout title="Store a Credential">
    <CredentialStorage client:only="react" />
</Layout>
```

{% endcode %}

Now, let's build out the `CredentialStorage` component! Let's start with a basic React component:

{% code title="src/components/CredentialStorage.tsx" lineNumbers="true" %}

```tsx
import React from 'react';

const CredentialStorage: React.FC = () => {
    return <div></div>;
};

export default CredentialStorage;
```

{% endcode %}

Now, let's figure out how to retrieve and display the requested credential!

#### Retrieving the Credential

In order to retrieve the credential, we can use the `receiveChapiEvent` method on a LearnCard wallet. This method is asynchronous, so we'll need to use state and an effect for this to work:

<pre class="language-tsx" data-title="src/components/CredentialStorage.tsx" data-line-numbers><code class="lang-tsx"><strong>import React, { useState, useEffect } from "react";
</strong><strong>import { initLearnCard } from "@learncard/init";
</strong>import type { CredentialStoreEvent } from "@learncard/chapi-plugin";

const CredentialStorage: React.FC = () => {
<strong>  const [event, setEvent] = useState&#x3C;CredentialStoreEvent>();
</strong>
  useEffect(() => {
    const fetchData = async () => {
      const learnCard = await initLearnCard();

      const _event = await learnCard.invoke.receiveChapiEvent();

      if ("credential" in _event) setEvent(_event);
    };
    fetchData();
  }, []);
<strong>  
</strong><strong>  if (!event) return &#x3C;h1>Loading...&#x3C;/h1>;
</strong>
  return &#x3C;div>&#x3C;/div>;
};

export default CredentialStorage;
</code></pre>

Because `learnCard.invoke.receiveChapiEvent` can technically be run on both a `store` and `get` page, we need to make TypeScript happy by checking that this is actually a `store` event. This is done by the `if ("credential" in _event")` check.

#### Displaying the Credential

In order to display the credential, we will use the `VCCard` component from `@learncard/react`. Before we can do that, however, we will need to first extract the credential from the raw event we received from CHAPI. To do this, we will create a helper that grabs a Verifiable Credential from a Verifiable Presentation:

{% code title="src/helpers/credential.helpers.ts" lineNumbers="true" %}

```typescript
import type { VP, VC } from '@learncard/types';

export const getCredentialFromVp = (vp: VP): VC => {
    const vcField = vp.verifiableCredential;

    return Array.isArray(vcField) ? vcField[0] : vcField;
};
```

{% endcode %}

By making use of this helper, we can now extract the credential and display it to the user.

```bash
bun add @learncard/react
```

<pre class="language-tsx" data-title="src/components/CredentialStorage.tsx" data-line-numbers><code class="lang-tsx">import React, { useState, useEffect } from "react";
import { initLearnCard } from "@learncard/init";
import type { CredentialStoreEvent } from "@learncard/chapi-plugin";
<strong>import { VCCard } from "@learncard/react";
</strong>
<strong>import "@learncard/react/dist/main.css";
</strong>
<strong>import { getCredentialFromVp } from "@helpers/credential.helpers";
</strong>
const CredentialStorage: React.FC = () => {
  const [event, setEvent] = useState&#x3C;CredentialStoreEvent>();

  useEffect(() => {
    const fetchData = async () => {
      const learnCard = await initLearnCard();

      const _event = await learnCard.invoke.receiveChapiEvent();

      if ("credential" in _event) setEvent(_event);
    };
    fetchData();
  }, []);

  if (!event) return &#x3C;h1>Loading...&#x3C;/h1>;

  const presentation = event?.credential?.data;

<strong>  const credential = presentation &#x26;&#x26; getCredentialFromVp(presentation);
</strong>
<strong>  return (
</strong><strong>    &#x3C;div className="w-full h-full flex flex-col justify-center items-center gap-4 p-4">
</strong><strong>      &#x3C;VCCard credential={credential} />
</strong><strong>    &#x3C;/div>
</strong><strong>  );
</strong>};

export default CredentialStorage;
</code></pre>

#### Storing the Credential

The final step for this page is to allow the user to either add an id and store this credential, or to reject this credential without storing it. Let's add that now!

{% code title="src/components/CredentialStorage.tsx" lineNumbers="true" %}

```tsx
import React, { useState, useEffect } from 'react';
import { initLearnCard } from '@learncard/init';
import type { CredentialStoreEvent } from '@learncard/chapi-plugin';
import { VCCard } from '@learncard/react';

import '@learncard/react/dist/main.css';

import { getCredentialFromVp } from '@helpers/credential.helpers';

const CredentialStorage: React.FC = () => {
    const [event, setEvent] = useState<CredentialStoreEvent>();
    const [id, setId] = useState('Test');

    useEffect(() => {
        const fetchData = async () => {
            const learnCard = await initLearnCard();

            const _event = await learnCard.invoke.receiveChapiEvent();

            if ('credential' in _event) setEvent(_event);
        };
        fetchData();
    }, []);

    if (!event) return <h1>Loading...</h1>;

    const accept = async () => {
        const learnCard = await initLearnCard({ seed: '1234' });

        const uri = await learnCard.store.LearnCloud.upload(credential);

        await learnCard.index.LearnCloud.add({ id, uri });

        event.respondWith(
            Promise.resolve({
                dataType: 'VerifiablePresentation',
                data: presentation,
            })
        );
    };

    const reject = () => event.respondWith(Promise.resolve(null));

    const presentation = event?.credential?.data;

    const credential = presentation && getCredentialFromVp(presentation);

    return (
        <form
            onSubmit={e => e.preventDefault()}
            className="w-full h-full flex flex-col justify-center items-center gap-4 p-4"
        >
            <VCCard credential={credential} />

            <fieldset>
                <label className="flex gap-2">
                    Title:
                    <input type="text" onChange={e => setId(e.target.value)} value={id} />
                </label>
            </fieldset>

            <fieldset className="flex gap-4">
                <button
                    type="button"
                    className="bg-green-200 rounded border px-4 py-2"
                    onClick={accept}
                >
                    Accept
                </button>
                <button
                    type="button"
                    className="bg-red-200 rounded border px-4 py-2"
                    onClick={reject}
                >
                    Reject
                </button>
            </fieldset>
        </form>
    );
};

export default CredentialStorage;
```

{% endcode %}

Phew! That was a lot of code! But now let's test it out! Head on over to <https://playground.chapi.io/issuer> and try issuing yourself a credential! You should ultimately land on a page like this:

<figure><img src="/files/luQzYqiCN9XKCuNPpEKC" alt=""><figcaption><p>CredentialStorage in action!</p></figcaption></figure>

<figure><img src="/files/HleoAWrJ5CxHc5yWYhTN" alt=""><figcaption><p>Backside of VC Card</p></figcaption></figure>

## Managing Credentials

Great! We've now set up a full CHAPI flow for storing credentials into a wallet! But what good is it to store credentials that you can't see? Let's head back to our Landing Page and add some Credential Management.

### CredentialListItem

We will want to display our credentials in an unordered list, so let's start with what a given list item will look like! To make things simple, we'll just display its title and add a delete button to allow the user to remove the credential from their wallet.

{% code title="src/components/CredentialListItem.tsx" lineNumbers="true" %}

```tsx
import React from 'react';
import { initLearnCard } from '@learncard/init';
import type { CredentialRecord } from '@learncard/types';

export type CredentialListItemProps = {
    credential: CredentialRecord;
};

const CredentialListItem: React.FC<CredentialListItemProps> = ({ credential: idxCredential }) => {
    const deleteCredential = async () => {
        const learnCard = await initLearnCard({ seed: '1234' });

        if (confirm('Are you sure you want to delete this credential?')) {
            await learnCard.index.IDX.remove(idxCredential.id);
            window.location.reload();
        }
    };

    return (
        <li className="rounded flex items-center overflow-hidden">
            <button type="button" className="w-full h-full bg-blue-100 py-2">
                {idxCredential.id}
            </button>

            <button
                type="button"
                onClick={deleteCredential}
                className="h-full bg-red-500 text-white border-l border-gray-300 p-2"
            >
                Delete
            </button>
        </li>
    );
};

export default CredentialListItem;
```

{% endcode %}

### Credentials

In order to actually render those List Items, we'll need to grab our credentials from the Wallet! Let's make a `Credentials` component to do this:

{% code title="src/components/Credentials.tsx" lineNumbers="true" %}

```tsx
import React, { useState, useEffect } from 'react';
import { initLearnCard, type LearnCardFromSeed } from '@learncard/init';
import type { CredentialRecord } from '@learncard/types';
import CredentialListItem from '@components/CredentialListItem';

import { VCDisplayCard } from '@learncard/react';

const Credentials: React.FC = () => {
    const [credentialsList, setCredentialsList] = useState<CredentialRecord[]>();
    const [learnCard, setLearnCard] = useState<LearnCardFromSeed['returnValue']>();

    useEffect(() => {
        initLearnCard({ seed: '1234' }).then(setLearnCard);
    }, []);

    useEffect(() => {
        if (learnCard) learnCard.index.LearnCloud.get().then(setCredentialsList);
    }, [learnCard]);

    if (!learnCard || !credentialsList) return <></>;

    const credentials =
        credentialsList.length === 0 ? (
            <>
                Looks like you don't have any credentials! Visit https://playground.chapi.io/issuer
                to add one!
            </>
        ) : (
            credentialsList.map(credential => (
                <CredentialListItem key={credential.title} credential={credential} />
            ))
        );

    return (
        <section className="max-w-5xl w-5/6 border rounded p-4 bg-gray-100">
            <header className="flex gap-2 justify-center items-center border-b pb-2 mb-2">
                <h2>Credentials</h2>
                <span className="text-gray-600 text-sm">(Click to view)</span>
            </header>
            <ul className="flex flex-col gap-2">{credentials}</ul>
        </section>
    );
};

export default Credentials;
```

{% endcode %}

Now let's actually render this on the index page!

{% code title="src/pages/index.astro" lineNumbers="true" %}

```tsx
---
import Layout from "@layouts/Layout.astro";
import Credentials from "@components/Credentials";
---

<Layout title="LearnCard CHAPI Example">
  <section id="modal-container"></section>

  <main class="w-full h-full flex flex-col justify-center items-center p-4">
    <header>
      <h1>LearnCard CHAPI Example</h1>
    </header>

    <h3 id="loading-wallet">Loading wallet...</h3>

    <Credentials client:only="react" />
  </main>
</Layout>

<script>
  import { initLearnCard } from "@learncard/init";

  const wallet = await initLearnCard({ seed: "1234" });

  await wallet.invoke.installChapiHandler();

  const loadingWallet = document.getElementById(
    "loading-wallet"
  ) as HTMLElement;

  loadingWallet.innerText = "Wallet loaded!";
</script>
```

{% endcode %}


# Using LearnCard to Interact with a CHAPI Wallet

Learn How to Store an Issued Verifiable Credential into a CHAPI compliant Wallet with LearnCard!

## Overview

Using [CHAPI](https://chapi.io/), Wallet software can register itself for a user, allowing it to become easily discoverable by a third party that would like to send a credential into that Wallet software. The result is a shared interface where issuers can simply send a credential off to CHAPI and allow the wallet to take care of everything else. The wallet software can then report back to the issuer whether or not it successfully stored the credential.

Let's use LearnCard to build out the issuer side of this flow together!

## Instantiating a LearnCard

As an issuer, we will need an *identity* in the form of a did. LearnCard allows us to determinstically create a did from a simple string. In a real application, you will want to use a true random source to generate one of these strings, and then store it somewhere very secure. However, for the purposes of this documentation, we will get by with the string `'a'` 😉

```typescript
import { initLearnCard } from '@learncard/init';

const learnCard = await initLearnCard({ seed: 'a' });
```

## Generating a Credential

Before we can ask a wallet to *store* a credential, we first need to *generate* a credential! There are many different ways to go about this, but `LearnCard` allows us to create a test credential rather easily!

```typescript
const testVc = learnCard.invoke.newCredential();
```

## Option 1: Easy DID Auth

Now that our credential is made, we can easily use CHAPI/DIDAuth to ask for the user's `did`, sign the credential, and store it all in one go!

```typescript
const result = await learnCard.invoke.storeCredentialViaChapiDidAuth(testVc);
```

### Reading the Result

The `result` object is a simple object with a `success` boolean, and a `reason` string. The four possible values it can hold, as well as what they represent are below:

<table><thead><tr><th width="394">Value</th><th>Explanation</th></tr></thead><tbody><tr><td><code>{ success: true }</code></td><td>The credential was successfully stored</td></tr><tr><td><code>{</code><br><code>success: false,</code><br><code>reason: 'did not auth'</code><br><code>}</code></td><td>The user rejected the DIDAuth request</td></tr><tr><td><code>{</code><br><code>success: false,</code><br><code>reason: 'auth failed verification'</code><br><code>}</code></td><td>The user's wallet software failed the verification challenge</td></tr><tr><td><code>{</code><br><code>success: false,</code><br><code>reason: 'did not store'</code><br><code>}</code></td><td>The user passed DID Authentication, but did not store the credential</td></tr></tbody></table>

## Option 2: Skipping DID Auth

### Signing a Credential

With our credential generated, we will need to *sign* it, which will allow verifying parties to prove that this credential actually came from our did!

```typescript
const vc = await learnCard.invoke.issueCredential(testVc);
```

### Generating/Signing a Presentation

With our credential signed and generated, we will now need to create a *presentation* that we can send off via CHAPI. This presentation will store the credential inside of it, and we can easily create and sign one with LearnCard!

```typescript
const unsignedVp = await learnCard.invoke.getTestVp(vc);
const vp = await learnCard.invoke.issuePresentation(unsignedVp);
```

### Storing a Presentation with CHAPI

Now that we've got our presentation made, we can send it off to CHAPI and ask a wallet to store it!

```typescript
const success = await learnCard.invoke.storePresentationViaChapi(vp);

if (success) console.log('Credential successfully stored!');
else console.log('Credential could not be stored');
```


# Cheat Sheets


# Issuers

Add the following code to issue credentials via CHAPI with LearnCard!

```typescript
import { initLearnCard } from '@learncard/init';

const learnCard = await initLearnCard({ seed: '1234' });

const vc = learnCard.invoke.newCredential(); // Or otherwise generate a credential, i.e. VC-API or OIDC

const result = await learnCard.invoke.storeCredentialViaChapiDidAuth(vc);

if (result.success) console.log('Credential stored! 😎');
```

{% hint style="info" %}
This cheat-sheet uses a test credential. Often, issuers will want to use VC-API or OIDCv4 protocols for signing the actual credential in tandem with CHAPI for selecting which wallet to send the credential to. Once you've got CHAPI setup, check out [LearnCard Bridge](broken://pages/AZysSjiEjB89UYGnmoZ9) to setup VC-API.
{% endhint %}

### More Info

{% content-ref url="/pages/VMHxIMHGxFJWs5ovijes" %}
[Using LearnCard to Interact with a CHAPI Wallet](/how-to-guides/implement-flows/chapi/using-learncard-to-interact-with-a-chapi-wallet)
{% endcontent-ref %}


# Wallets

Add the following files/routes to your app to become CHAPI compliant!

{% code title="/" %}

```typescript
import { initLearnCard } from '@learncard/init';

const learnCard = await initLearnCard();

await learnCard.invoke.installChapiHandler();
```

{% endcode %}

{% code title="/manifest.json" %}

```json
{
  "name": "LearnCard Demo CHAPI Wallet",
  "short_name": "LearnCard Demo CHAPI Wallet",
  "icons": [
    {
      "sizes": "64x64",
      "src": "icon.png",
      "type": "image/png"
    }

  ],
  "credential_handler": {
    "url": "/wallet-worker",
    "enabledTypes": ["VerifiablePresentation"]
  }
}
```

{% endcode %}

{% code title="/wallet-worker" %}

```typescript
import { initLearnCard } from '@learncard/init';

const learnCard = await initLearnCard();

learnCard.invoke.activateChapiHandler({
    get: async () => {
        // Return an arbitrary route to display to users when requesting a credential
        // or using DIDAuth with your application
        return { type: 'redirect', url: `${window.location.origin}/get` };
    },
    store: async () => {
        // Return an arbitrary route to display to users when storing a credential
        // with your application
        return { type: 'redirect', url: `${window.location.origin}/store` };
    },
});
```

{% endcode %}

{% code title="/store" %}

```typescript
import { initLearnCard } from '@learncard/init';

const learnCard = await initLearnCard({ seed });

const event = await learnCard.invoke.receiveChapiEvent();

const vp = event.credential.data;

const vc = Array.isArray(vp.verifiableCredential)
    ? vp.verifiableCredential[0]
    : vp.verifiableCredential;
    
const accept = async () => {
    const uri = await learnCard.store.Ceramic.upload(vc);
    await wallet.index.IDX.add({ uri, id });
    event.respondWith(Promise.resolve({ dataType: 'VerifiablePresentation', data: vp }); 
};

const reject = () => event.respondWith(Promise.resolve(null));
```

{% endcode %}

{% code title="/get" %}

```typescript
import { initLearnCard } from '@learncard/init';

const learnCard = await initLearnCard({ seed });

const event = await learnCard.invoke.receiveChapiEvent();
const origin = event.credentialRequestOrigin;

const accept = () => {
    const presentation = event.credentialRequestOptions.web.VerifiablePresentation;
    const { challenge, domain } = presentation;

    event.respondWith(
        Promise.resolve({
            dataType: 'VerifiablePresentation',
            data: await learnCard.invoke.issuePresentation(await learnCard.invoke.getTestVp(), {
                challenge,
                domain,
                proofPurpose: 'authentication',
            }),
        })
    );
};

const reject = () => event.respondWith(Promise.resolve(null));
```

{% endcode %}

### More Info

{% content-ref url="/pages/9OUXr96cFl5PkSW71flf" %}
[CHAPI Wallet Setup Guide](/how-to-guides/implement-flows/chapi/chapi-wallet-setup-guide)
{% endcontent-ref %}

{% content-ref url="/pages/zk9ScZY7S9mndeLVUMZt" %}
[Demo Application](/how-to-guides/implement-flows/chapi/demo-application)
{% endcontent-ref %}


# Deploy Infrastructure


# Remote Key Management

Remote Key Management System (KMS) for LearnCard

This documentation will guide you through the process of implementing a remote Key Management System (KMS) for deriving a deterministic, 64-character seed phrase to construct your LearnCard wallet.

### Overview

In some cases, you may want to store and manage the seed phrases for LearnCard wallets in a remote and secure KMS. This approach allows for better control and security of the seed phrases while providing flexibility in managing multiple wallets for different use cases.

### Implementation Steps

1. **Set up the remote KMS**: Choose a suitable KMS provider that meets your requirements for security, scalability, and performance. Some popular KMS providers include AWS KMS, Google Cloud KMS, and Azure Key Vault. Set up your account and create a dedicated key store for your LearnCard seed phrases.
2. **Generate seed phrases**: Generate a deterministic, 64-character seed phrase using your chosen KMS provider's API or SDK. This seed phrase will be used to derive the keys for your LearnCard wallet.
3. **Securely store seed phrases**: Store the generated seed phrase securely in the remote KMS, ensuring proper access control and encryption are in place.
4. **Retrieve seed phrases**: When needed, retrieve the seed phrase from the remote KMS using a secure connection and authentication mechanism provided by your KMS provider.
5. **Initialize LearnCard wallet**: Import the `initLearnCard` function from the `@learncard/init` package, and initialize the LearnCard wallet using the retrieved seed phrase.

```javascript
import { initLearnCard } from '@learncard/init';

const seed = getSeedFromKMS(); // Replace with the seed retrieved from your remote KMS
const learnCard = await initLearnCard({ seed });
```

By following these steps, you can leverage a remote KMS for managing your LearnCard wallet's seed phrases, providing enhanced security and control over the wallet initialization process.


# SSS Key Management Config

Configure and deploy SSS key management for your LearnCard instance

This guide covers the environment variables and infrastructure needed to deploy LearnCard with the self-hosted Shamir Secret Sharing (SSS) key management system.

## Prerequisites

* A running **lca-api** server instance
* **Redis** (for OTP codes, QR login sessions, and caching)
* **MongoDB** (for UserKey records)
* A **Firebase** project (or other supported auth provider) with Admin SDK credentials
* **Postmark** account (for email delivery in production) — optional, falls back to logging

***

## LCA API Server Environment Variables

### Required

| Variable                        | Description                                                                                                                                                                          | Example                          |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------- |
| `SEED`                          | Server-side encryption seed. Used to derive the KEK for AES-256-GCM auth share encryption at rest. **Critical for security — if lost, all stored auth shares become unrecoverable.** | A long, random, secret string    |
| `REDIS_HOST`                    | Redis hostname. **Must be `REDIS_HOST`**, not `REDIS_URL` — the cache module reads this specific variable.                                                                           | `redis.example.com`              |
| `REDIS_PORT`                    | Redis port.                                                                                                                                                                          | `6379`                           |
| `GOOGLE_APPLICATION_CREDENTIAL` | Firebase Admin SDK service account JSON (stringified). Used for token verification, user management, and custom token generation.                                                    | `{"type":"service_account",...}` |

### Email Delivery (Production)

Emails are rendered locally via [`@learncard/email-templates`](/core-concepts/tenant-branded-emails) and delivered through Postmark as raw HTML. Tenant branding (brand name, logo, colors, from-domain) is applied automatically based on the `X-Tenant-Id` / `Origin` header on each request. See [Configure Tenant-Branded Emails](https://github.com/learningeconomy/LearnCard/tree/main/docs/how-to-guides/configure-tenant-branded-emails.md) for how to register new tenants.

| Variable                                      | Description                                                                                                                                                                    | Example                                |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------- |
| `POSTMARK_SERVER_TOKEN`                       | Postmark API key. If unset, email delivery falls back to console logging.                                                                                                      | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` |
| `POSTMARK_FROM_EMAIL`                         | Default "From" email address. Overridden per-email by `getFrom()` when tenant `fromDomain` is set.                                                                             | `noreply@example.com`                  |
| `POSTMARK_BRAND_NAME`                         | Legacy brand name. Used only when the active tenant has no `brandName` override.                                                                                               | `LearnCard`                            |
| `DEFAULT_TENANT_ID`                           | *(Optional)* Fallback tenant ID used when neither `X-Tenant-Id` nor `Origin` resolves to a known tenant. Useful for per-tenant deploys and cron jobs. Defaults to `learncard`. | `vetpass`                              |
| `POSTMARK_LOGIN_CODE_TEMPLATE_ALIAS`          | *(Optional override)* Real Postmark template alias for login OTP emails. When set, the adapter still renders locally first; this value is only used if local rendering fails.  | `login-code`                           |
| `POSTMARK_ENDORSEMENT_REQUEST_TEMPLATE_ALIAS` | *(Optional override)* Same as above, for endorsement request emails.                                                                                                           | `endorsement-request`                  |
| `POSTMARK_RECOVERY_EMAIL_CODE_TEMPLATE_ALIAS` | *(Optional override)* Same as above, for recovery email verification codes.                                                                                                    | `recovery-email-code`                  |
| `POSTMARK_RECOVERY_KEY_TEMPLATE_ALIAS`        | *(Optional override)* Same as above, for recovery key backup emails.                                                                                                           | `recovery-key`                         |

{% hint style="info" %}
**Migration from plain-text fallbacks (pre-LC-1749)**

Earlier deployments required `POSTMARK_RECOVERY_EMAIL_CODE_TEMPLATE_ALIAS` and `POSTMARK_RECOVERY_KEY_TEMPLATE_ALIAS` to avoid falling back to unstyled plain-text emails. These env vars are now pure overrides — the server always renders the React Email template from `@learncard/email-templates` with full tenant branding. You may remove them from your deployment configuration.
{% endhint %}

{% hint style="warning" %}
The template variables changed from numeric IDs to string aliases in an earlier release:

* `POSTMARK_LOGIN_CODE_TEMPLATE_ID` → `POSTMARK_LOGIN_CODE_TEMPLATE_ALIAS`
* `POSTMARK_ENDORSEMENT_REQUEST_TEMPLATE_ID` → `POSTMARK_ENDORSEMENT_REQUEST_TEMPLATE_ALIAS`

Update your deployment configuration accordingly.
{% endhint %}

### Monitoring (Optional)

| Variable     | Description                                                  | Example                     |
| ------------ | ------------------------------------------------------------ | --------------------------- |
| `SENTRY_DSN` | Sentry DSN for error tracking. If unset, Sentry is disabled. | `https://xxx@sentry.io/123` |
| `SENTRY_ENV` | Sentry environment label.                                    | `production`                |

### Test / Development Modes

| Variable      | Description                                                                                                      |
| ------------- | ---------------------------------------------------------------------------------------------------------------- |
| `IS_OFFLINE`  | Set to `'true'` to bypass Firebase token verification (parses JWT locally). For local development.               |
| `IS_E2E_TEST` | Set to `'true'` to enable E2E test mode: email delivery uses log adapter, Firebase Admin SDK calls are bypassed. |

***

## Frontend Environment Variables (LearnCard App)

The frontend reads environment variables with a dual-prefix fallback: `VITE_*` first, then `REACT_APP_*`.

### SSS Configuration

| Variable (Vite)       | Variable (CRA)                      | Default                       | Description                                                                 |
| --------------------- | ----------------------------------- | ----------------------------- | --------------------------------------------------------------------------- |
| `VITE_KEY_DERIVATION` | `REACT_APP_KEY_DERIVATION_PROVIDER` | `'sss'`                       | Key derivation strategy. Set to `'sss'` for SSS or `'web3auth'` for legacy. |
| `VITE_SSS_SERVER_URL` | `REACT_APP_SSS_SERVER_URL`          | `'http://localhost:5100/api'` | LCA API base URL for SSS key operations.                                    |
| `VITE_AUTH_PROVIDER`  | `REACT_APP_AUTH_PROVIDER`           | `'firebase'`                  | Auth provider. Currently only `'firebase'` is implemented.                  |

### Feature Flags

| Variable                             | Default   | Description                                                                        |
| ------------------------------------ | --------- | ---------------------------------------------------------------------------------- |
| `VITE_ENABLE_EMAIL_BACKUP_SHARE`     | `'true'`  | Auto-send backup share to user's email during key setup. Set `'false'` to disable. |
| `VITE_REQUIRE_EMAIL_FOR_PHONE_USERS` | `'true'`  | Require phone-only users to link an email before proceeding.                       |
| `VITE_ENABLE_AUTH_DEBUG_WIDGET`      | `'false'` | Show auth/key debug overlay. Auto-enabled in dev mode.                             |

### Web3Auth (Migration Period)

These are still read to support the legacy `web3auth` key derivation fallback during migration:

| Variable (Vite)             | Variable (CRA)                   | Description                  |
| --------------------------- | -------------------------------- | ---------------------------- |
| `VITE_WEB3AUTH_CLIENT_ID`   | `REACT_APP_WEB3AUTH_CLIENT_ID`   | Web3Auth dashboard client ID |
| `VITE_WEB3AUTH_NETWORK`     | `REACT_APP_WEB3AUTH_NETWORK`     | Web3Auth network             |
| `VITE_WEB3AUTH_VERIFIER_ID` | `REACT_APP_WEB3AUTH_VERIFIER_ID` | Web3Auth verifier name       |
| `VITE_WEB3AUTH_RPC_TARGET`  | `REACT_APP_WEB3AUTH_RPC_TARGET`  | Ethereum RPC URL             |

Once all users are migrated, these can be removed.

***

## Infrastructure Requirements

### Redis

Redis is required for:

* **OTP codes** for login and recovery email verification (stored with short TTLs)
* **QR login sessions** (ephemeral, auto-evicted)
* **General caching**

A single Redis instance is sufficient. The lca-api connects via `REDIS_HOST` + `REDIS_PORT`.

### MongoDB

MongoDB stores the `UserKey` collection, which contains:

* The user's encrypted auth share
* Previous auth share versions (for share versioning)
* Recovery method metadata
* Contact method and DID associations

### Firebase Admin SDK

The Firebase Admin SDK is used for:

* **Token verification** — validating Firebase ID tokens from the frontend
* **User management** — updating user email/phone during contact method upgrades
* **Custom tokens** — issuing new Firebase auth tokens after contact method changes

The service account JSON is provided via `GOOGLE_APPLICATION_CREDENTIAL`.

***

## Deployment Checklist

1. **Set `SEED`** on the lca-api server. This is the most critical secret — back it up securely. If lost, all stored auth shares become permanently unrecoverable.
2. **Set `REDIS_HOST`** (not `REDIS_URL`) and `REDIS_PORT` on the lca-api server.
3. **Set `GOOGLE_APPLICATION_CREDENTIAL`** with the Firebase Admin SDK service account JSON.
4. **Configure Postmark** (production only): set `POSTMARK_SERVER_TOKEN`, `POSTMARK_FROM_EMAIL`, `POSTMARK_BRAND_NAME`, and the template alias variables.
5. **Set frontend env vars**: `VITE_KEY_DERIVATION=sss` and `VITE_SSS_SERVER_URL` pointing to your lca-api instance.
6. **Verify** by logging in with a new account and checking that the AuthCoordinator reaches the `ready` state.

***

## Troubleshooting

| Symptom                         | Likely Cause                            | Fix                                                   |
| ------------------------------- | --------------------------------------- | ----------------------------------------------------- |
| OTP codes not working           | lca-api using in-memory Redis mock      | Verify `REDIS_HOST` is set (not `REDIS_URL`)          |
| "Failed to verify token" errors | Missing or invalid Firebase credentials | Check `GOOGLE_APPLICATION_CREDENTIAL`                 |
| Auth shares not persisting      | MongoDB connection issue                | Check `LCA_API_MONGO_URI` and `LCA_API_MONGO_DB_NAME` |
| Emails not sending              | Postmark not configured                 | Set `POSTMARK_SERVER_TOKEN` and `POSTMARK_FROM_EMAIL` |


# Generate API Tokens

{% hint style="info" %}
Click here for more information on [Auth Grants and Scopes](/core-concepts/architecture-and-principles/auth-grants-and-api-tokens).
{% endhint %}

### How to Generate API Tokens

Here's an example showing how to:

1. Create an AuthGrant
2. Generate an API token
3. Use the token to send a boost via the HTTP API

```javascript
// Step 1: Create an AuthGrant with specific permissions
const grantId = await learnCard.invoke.addAuthGrant({
    name: "Boost Sender Auth",
    description: "Permission to send boosts",
    scope: 'boosts:write',
});

// Step 2: Generate an API token from the AuthGrant
const token = await learnCard.invoke.getAPITokenForAuthGrant(grantId);

// Step 3: Prepare the payload for your API request
const payload = {
    boostUri: "uri-of-the-boost-to-send",
    signingAuthority: "your-signing-authority"
};

// Step 4: Make an authenticated HTTP request using the token
const response = await fetch(
    `https://network.learncard.com/api/boost/send/via-signing-authority/RECIPIENT_PROFILE_ID`,
    {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${token}`,
        },
        body: JSON.stringify(payload),
    }
);

// Step 5: Process the response
if (response.status === 200) {
    const sentBoostUri = await response.json();
    console.log(`Boost sent successfully: ${sentBoostUri}`);
} else {
    console.error(`Error sending boost: ${response.status}`);
    const errorDetails = await response.json();
    console.error(errorDetails);
}
```

### Managing API Tokens in LearnCardApp

#### Steps to Create an API Token

1. **Navigate to Your Profile:**
   * Go to **Developer Tools** > **API Tokens**.
2. **Create an API Token:**
   * **Click**: Create an API Token
   * **Provide the Following Information:**
     * **Name** (required)
     * **Description** (optional)
     * **Scope** (required)
     * **Expiration** (optional)
   * **Click**: Create
3. Already Signed In? Deep link below 👇\
   \- [LearnCardApp API Token Dev Tools](https://learncard.app/passport?showTokenDevTools=true)

#### Token Management Features

* **Revoke/Delete a Token**
* **Update an Existing Token**
* **View Token Details**: Including status, token, creation date, expiration, and scope.

{% embed url="<https://www.loom.com/share/fe1901f8e3344f26b9dffdc7cd4bfff7>" %}


# Deploy Your Own Network


# Connect to Independent Network

Setting Up ConsentFlow with an Independent Network

ConsentFlow contracts allow third-party applications to read from and write to a LearnCard with the consent of the LearnCard owner. This guide will walk you through setting up a ConsentFlow with an Independent **Network** (instead of the default LearnCard Network) and Independent **Wallet** (instead of the default LearnCard App) to connect your external, 3rd party application.

### Prerequisites

* Node.js environment
* Access to an Independent Network endpoint
* Access to an Independent Wallet Application

### Step 1: Create a Service Profile

First, you need to initialize the LearnCard CLI and create a network LearnCard that points to your Independent Network:

```javascript
// Install and start LearnCard CLI
// bunx @learncard/cli

// Initialize LearnCard with your Independent Network
const networkLearnCard = await initLearnCard({
    seed: '[your secure key]',
    network: 'https://network.independent.example.org/trpc', // Point to your Independent Network
});

// Create a service profile
const serviceProfile = {
    displayName: 'Your App Name',
    profileId: 'your-app-unique-id',
    image: 'https://example.com/your-app-logo.jpg',
};

// Register the service profile
await networkLearnCard.invoke.createServiceProfile(serviceProfile);
```

Your service profile represents your application in the LearnCard ecosystem. Make sure to use a unique `profileId` and provide a clear `displayName` and `image` to help users recognize your service.

### Step 2: Create a ConsentFlow Contract

Next, create a ConsentFlow contract that specifies what data your application needs to read from and write to users' LearnCard wallets:

```javascript
const consentFlowContract = {
    'name': 'Your App Integration',
    'subtitle': 'Connect your LearnCard to Your App',
    'description':
        'This connection allows Your App to access and issue credentials on your LearnCard.',
    'image': 'https://example.com/your-app-logo.jpg',
    'redirectUrl': 'https://your-app.com/callback', // Where to redirect after consent
    'contract': {
        'read': {
            'anonymize': true, // Set to false if you need identifiable information
            'credentials': {
                'categories': {
                    // Specify credential categories your app needs to read
                    'Merit Badge': {
                        'required': true,
                    },
                    'Skill': {
                        'required': false,
                    },
                },
            },
            'personal': {
                'Name': {
                    'required': false,
                },
            },
        },
        'write': {
            'credentials': {
                'categories': {
                    // Specify credential categories your app will write
                    'Merit Badge': {
                        'required': true,
                    },
                    'Skill': {
                        'required': false,
                    },
                },
            },
            'personal': {
                'SomeCustomID': {
                    'required': true,
                },
            },
        },
    },
};

// Create the contract and get its URI
const contractUri = await networkLearnCard.invoke.createContract(consentFlowContract);

// Generate consent flow URLs for your Independent Wallet connected to your Network
const productionUrl = `https://wallet.independent.example.org/consent-flow?uri=${contractUri}`;

console.log('Production ConsentFlow URL:', productionUrl);
```

#### Available Credential Categories

When specifying credential categories in your contract, ensure you are using categories supported by your network. For example, many networks support a subset of the following categories:

* Achievement
* Accommodation
* Accomplishment
* Course
* ID
* Job
* Learning History
* Membership
* Merit Badge
* Skill
* Social Badge
* Work History

### Step 3: Add a "Connect Your Wallet" Button to Your Website

Add a button or link on your application that directs users to the ConsentFlow URL:

```html
<a
    href="https://wallet.independent.example.org/consent-flow?uri=YOUR_CONTRACT_URI"
    class="connect-button"
>
    Connect Your Wallet
</a>
```

When users click this button, they'll be directed to the ConsentFlow consent page where they can review and approve the permissions your app is requesting.

### Step 4: Handle the Redirect After User Consent

After a user consents to your contract, they'll be redirected to the URL specified in your contract's `redirectUrl` parameter with their DID added as a query parameter:

```
https://your-app.com/callback?did=did:method:profile-id
```

In your application, implement a handler for this callback:

```javascript
// Example Express.js route handler
app.get('/callback', (req, res) => {
    const userDid = req.query.did;

    if (!userDid) {
        return res.status(400).send('Missing DID parameter');
    }

    // Store the DID in your database, associating it with the user's account
    storeUserDid(currentUser.id, userDid)
        .then(() => {
            res.redirect('/dashboard?connection=success');
        })
        .catch(error => {
            console.error('Failed to store user DID:', error);
            res.status(500).send('Failed to complete connection');
        });
});

// Function to store the DID in your database
async function storeUserDid(userId, did) {
    // Implementation depends on your database
    await db.users.update({
        where: { id: userId },
        data: { learnCardDid: did },
    });
}
```

### Step 5: Reading Data from Connected LearnCards

To read data from users who have consented to your contract:

```javascript
// Retrieve ConsentFlow data for your contract
async function getLearnCardData(contractUri, options = {}) {
    let data = await networkLearnCard.invoke.getConsentFlowData(contractUri, options);

    // Process the returned records
    for (const record of data.records) {
        // Access shared credentials by category
        const meritBadges = record.credentials.categories['Merit Badge'] || [];

        for (const credentialUri of meritBadges) {
            // Read the credential
            const credential = await networkLearnCard.read.get(credentialUri);
            // Process the credential
            console.log('Retrieved credential:', credential);
        }

        // Access personal information if shared
        const userName = record.personal['Name'];

        // Store or process the retrieved data
    }

    // Handle pagination if there are more records
    if (data.hasMore) {
        // Get the next page
        const nextPageData = await getLearnCardData(contractUri, { cursor: data.cursor });
        // Combine with current data
        data.records = [...data.records, ...nextPageData.records];
    }

    return data;
}
```

### Step 6: Sending Credentials to Users

#### Option A: Using LearnCard SDK

To issue credentials to connected users with the LearnCard SDK:

```javascript
// Function to send a credential to a user by their DID
async function sendCredentialToUser(did, credentialData) {
  // Extract the profileId from the DID
  // This is a simplified example - you'll need to implement proper DID resolution
  const profileId = did.split(':').pop();

  // Create a new credential
  const newCredential = networkLearnCard.invoke.newCredential({
        type: 'boost',
        boostName: 'Hello from External App',
        boostImage: 'https://placehold.co/400x400?text=External+App',
        achievementType: 'Achievement',
        achievementName:'Connected External App',
        achievementDescription: 'Awarded for connecting to a 3rd party app.',
        achievementNarrative: 'Created and connected a full external app.'
        achievementImage: 'https://placehold.co/400x400?text=External+App',
    });

  // Issue the credential
  const vc = await networkLearnCard.invoke.issueCredential(newCredential);

  // Send the credential to the user (encrypted for security)
  const encrypt = true;
  await networkLearnCard.invoke.sendCredential(profileId, vc, encrypt);

  return vc;
}
```

#### Option B: Using HTTP Endpoints

If you prefer not to use the LearnCard npm packages, you can send credentials directly using HTTP endpoints. This approach is useful for integrations in languages other than JavaScript or in environments where installing npm packages might be challenging.

```javascript
/**
 * Send a credential to a user by their profileId using HTTP endpoints
 * @param {string} profileId - The user's profile ID
 * @param {object} credential - The credential to send
 * @param {string} apiBaseUrl - The base URL for the network API
 * @param {string} secretToken - Your authorization token
 */
async function sendCredentialViaHttp(profileId, credential, apiBaseUrl, secretToken) {
    const endpoint = `${apiBaseUrl}/api/credential/send/${profileId}`;

    try {
        const response = await fetch(endpoint, {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${secretToken}`,
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ credential }),
        });

        if (!response.ok) {
            throw new Error(`HTTP error! Status: ${response.status}`);
        }

        const data = await response.json();
        return data;
    } catch (error) {
        console.error('Failed to send credential:', error);
        throw error;
    }
}
```

**Example Usage with a Complete Credential**

```javascript
// First, extract the profileId from the user's DID that you stored
const profileId = userDid.split(':').pop(); // Simplified example

// Define a complete credential
const credential = {
    '@context': [
        'https://www.w3.org/2018/credentials/v1',
        'https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json',
    ],
    'id': 'https://yourapp.com/credentials/1234',
    'type': ['VerifiableCredential', 'OpenBadgeCredenial'],
    'issuer': 'did:example:issuer',
    'issuanceDate': new Date().toISOString(),
    'name': 'Course Completion',
    'credentialSubject': {
        'id': userDid, // The user's DID
        'type': ['AchievementSubject'],
        'achievement': {
            'id': 'https://yourapp.com/achievements/course-completion',
            'type': ['Achievement'],
            'name': 'Course Completion',
            'description': 'Successfully completed the Introduction to Blockchain course',
            'criteria': {
                'narrative': 'Completed all modules with a score of 85% or higher',
            },
            'image': 'https://yourapp.com/badges/blockchain-intro.png',
        },
    },
};

// Send the credential
const apiBaseUrl = 'https://network.independent.example.org/api'; // Your network's base URL
const secretToken = 'YOUR_SECRET_TOKEN'; // Your authorization token

sendCredentialViaHttp(profileId, credential, apiBaseUrl, secretToken)
    .then(response => {
        console.log('Credential sent successfully:', response);
    })
    .catch(error => {
        console.error('Failed to send credential:', error);
    });
```

**Implementation in Other Languages**

**Python Example**

```python
import requests
import json
from datetime import datetime, timedelta

def send_credential_via_http(profile_id, credential, api_base_url, secret_token):
    """
    Send a credential to a user by their profileId using HTTP endpoints

    Args:
        profile_id (str): The user's profile ID
        credential (dict): The credential to send
        api_base_url (str): The base URL for the network API
        secret_token (str): Your authorization token

    Returns:
        dict: The response from the API
    """
    endpoint = f"{api_base_url}/api/credential/send/{profile_id}"

    headers = {
        "Authorization": f"Bearer {secret_token}",
        "Content-Type": "application/json"
    }

    try:
        response = requests.post(
            endpoint,
            headers=headers,
            data=json.dumps({"credential": credential})
        )

        response.raise_for_status()  # Raise an exception for HTTP errors

        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"Error sending credential: {e}")
        raise
```

### Best Practices

1. **Security**: Always use a strong, secure seed for your LearnCard initialization and keep your secret tokens secure.
2. **Privacy**: Only request access to the credential categories and personal information that your application actually needs.
3. **User Experience**: Clearly explain to users why your application needs access to their LearnCard and what benefits they'll receive.
4. **Error Handling**: Implement robust error handling for all LearnCard operations and HTTP requests.
5. **Persistence**: Store the contract URI securely - you'll need it for all future operations with that contract.
6. **Authentication**: When using HTTP endpoints, ensure proper authentication mechanisms are in place to protect sensitive operations.
7. **Validation**: Always validate credential data before sending to ensure it meets the required format and schema.

### Step 7: Working with Boosts

Boosts are a powerful feature that allow you to create reusable credential templates that can be sent to multiple recipients while maintaining analytics and management capabilities.

#### What is a Boost?

A Boost is a registered credential template in the network that can be:

* Sent to multiple recipients
* Tracked with analytics
* Managed through permissions
* Organized into categories

#### Why Use Boosts Instead of Direct Credential Sending

Understanding the difference between Boosts and direct credential sending is important:

| Feature      | Direct Credential Sending         | Boosts                                         |
| ------------ | --------------------------------- | ---------------------------------------------- |
| Analytics    | No analytics available            | Track how many users claimed, viewed analytics |
| Recipients   | One credential per send operation | Same template can be sent to multiple users    |
| Management   | No central management             | Can edit, update, and manage permissions       |
| Organization | No categorization                 | Can be organized by type and category          |
| Permissions  | Limited control                   | Fine-grained permission management             |

#### Creating a Boost Using HTTP Endpoints

You can create a Boost using the HTTP API:

```javascript
/**
 * Create a boost using HTTP endpoints
 * @param {object} boostData - The boost definition
 * @param {string} apiBaseUrl - The base URL for the network API
 * @param {string} secretToken - Your authorization token
 */
async function createBoostViaHttp(boostData, apiBaseUrl, secretToken) {
    const endpoint = `${apiBaseUrl}/api/boost/create`;

    try {
        const response = await fetch(endpoint, {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${secretToken}`,
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(boostData),
        });

        if (!response.ok) {
            throw new Error(`HTTP error! Status: ${response.status}`);
        }

        const data = await response.json();
        return data;
    } catch (error) {
        console.error('Failed to create boost:', error);
        throw error;
    }
}
```

**Example Boost Creation**

Here's a complete example of creating a merit badge Boost:

```javascript
javascriptCopy; // Define the boost data
const meritBadgeBoost = {
    'name': 'Knot Master',
    'type': 'ext:KnotMaster',
    'category': 'Social Badge',
    'status': 'LIVE', // DRAFT or LIVE
    'credential': {
        '@context': [
            'https://www.w3.org/2018/credentials/v1',
            'https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.1.json',
            'https://ctx.learncard.com/boosts/1.0.3.json',
        ],
        'attachments': [],
        'credentialSubject': {
            'achievement': {
                'achievementType': 'ext:KnotMaster',
                'criteria': {
                    'narrative':
                        'The Friendship Knot Fanatic Badge is awarded to the Scout demonstrating a consistent and passionate dedication to tying friendship knots.',
                },
                'description': 'For the Scout tying friendship knots every day!',
                'id': 'urn:uuid:123',
                'image': 'https://cdn.filestackcontent.com/CKa9uvnqTrWYHlG2B5af',
                'name': 'Friendship Knot Fanatic',
                'type': ['Achievement'],
            },
            'type': ['AchievementSubject'],
        },
        'display': {
            'backgroundColor': '',
            'backgroundImage': '',
            'displayType': '',
            'emoji': {
                'activeSkinTone': '',
                'imageUrl': '',
                'names': [],
                'unified': '',
                'unifiedWithoutSkinTone': '',
            },
        },
        'groupID': '',
        'image': 'https://cdn.filestackcontent.com/CKa9uvnqTrWYHlG2B5af',
        'issuanceDate': '2025-04-04T19:31:38.373Z',
        'name': 'Friendship Knot Fanatic',
        'skills': [],
        'type': ['VerifiableCredential', 'OpenBadgeCredential', 'BoostCredential'],
    },
};

// Create the boost
const apiBaseUrl = 'https://network.independent.example.org/api'; // Your network's base URL
const secretToken = 'YOUR_SECRET_TOKEN'; // Your authorization token

createBoostViaHttp(meritBadgeBoost, apiBaseUrl, secretToken)
    .then(response => {
        console.log('Boost created successfully with URI:', response.uri);
        // Store this URI for future use when sending the boost
    })
    .catch(error => {
        console.error('Failed to create boost:', error);
    });
```

#### Sending a Boost to Recipients

Once you've created a Boost, you can send it to recipients using their profileId:

```javascript
/**
 * Send a boost to a user by their profileId using HTTP endpoints
 * @param {string} profileId - The recipient's profile ID
 * @param {string} boostUri - The URI of the boost to send
 * @param {object} credential - Optional: customized credential for this recipient
 * @param {object} options - Optional: additional options like skipNotification
 * @param {string} apiBaseUrl - The base URL for the network API
 * @param {string} secretToken - Your authorization token
 */
async function sendBoostViaHttp(profileId, boostUri, credential, options, apiBaseUrl, secretToken) {
    const endpoint = `${apiBaseUrl}/api/boost/send/${profileId}`;

    const payload = {
        uri: boostUri,
        credential: credential, // Optional: if you want to customize the credential for this recipient
    };

    if (options) {
        payload.options = options;
    }

    try {
        const response = await fetch(endpoint, {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${secretToken}`,
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(payload),
        });

        if (!response.ok) {
            throw new Error(`HTTP error! Status: ${response.status}`);
        }

        const data = await response.json();
        return data;
    } catch (error) {
        console.error('Failed to send boost:', error);
        throw error;
    }
}
```

#### Best Practices for Working with Boosts

1. **Create Reusable Templates**: Design your Boosts to be reusable across multiple recipients.
2. **Use Meaningful Categories**: Organize Boosts into logical categories for easier management.
3. **Set Appropriate Permissions**: Define who can issue, edit, and manage your Boosts.
4. **Use DRAFT Status**: When creating a new Boost, set it to DRAFT until you're ready to start issuing.

### Troubleshooting

* **Connection Issues**: Ensure your network endpoint is correctly configured and accessible.
* **Missing DIDs**: Verify that users are completing the consent process and your redirect handler is properly extracting the DID.
* **Credential Read Failures**: Check that your contract has the necessary read permissions for the credential categories you're trying to access.
* **Credential Write Failures**: Verify that your contract has the necessary write permissions and that you're using the correct profileId when sending credentials.
* **HTTP Errors**: For HTTP endpoint issues, check your authorization token, ensure the correct endpoint URL, and verify your payload structure meets the API requirements.
* **Authentication Problems**: Ensure you're using the correct authorization token and that it hasn't expired.
* **Profile ID Format**: When extracting profileId from a DID, make sure you're using the correct format required by the network.
* **Boost Creation Failures**: Verify that all required fields are provided and properly formatted in your Boost creation request.
* **Boost Sending Issues**: Ensure the Boost URI is valid and that you have permission to issue the Boost.

By following this guide, you should be able to successfully integrate LearnCard with your application using an Independent Network, allowing users to connect their LearnCards and exchange credentials securely.


# Build a Plugin

The simplest plugin

## Quick Start

### Boilerplate

Start with a basic TypeScript node package boilerplate. If you've never done that before, we recommend using [aqu](https://www.npmjs.com/package/aqu?activeTab=readme):

{% hint style="info" %}
If you don't have aqu installed, you can install it globally with `npm i -g aqu.`
{% endhint %}

{% tabs %}
{% tab title="Bun" %}

<pre class="language-bash"><code class="lang-bash"><strong>bunx aqu create simple-plugin
</strong>
? Pick package manager: bun
? Specify package description: ()
? Package author:
? Git repository (only for package.json information):
? Pick license: MIT
<strong>? Pick template: typescript
</strong>
cd simple-plugin
</code></pre>

{% endtab %}

{% tab title="yarn" %}

<pre class="language-bash"><code class="lang-bash"><strong>yarn dlx aqu create simple-plugin
</strong>
? Pick package manager: yarn
? Specify package description: ()
? Package author:
? Git repository (only for package.json information):
? Pick license: MIT
<strong>? Pick template: typescript
</strong>
cd simple-plugin
</code></pre>

{% endtab %}

{% tab title="npm" %}

<pre class="language-bash"><code class="lang-bash"><strong>npx aqu create simple-plugin
</strong>
? Pick package manager: npm
? Specify package description: ()
? Package author:
? Git repository (only for package.json information):
? Pick license: MIT
<strong>? Pick template: typescript
</strong>
cd simple-plugin
</code></pre>

{% endtab %}
{% endtabs %}

{% hint style="info" %}
If you'd like to publish your plugin to npm for others to use, please see our documentation on [publishing plugins](#publish-your-plugin-to-npm)
{% endhint %}

### Install Dependencies

Using your preferred package manager, install `@learncard/core` (for the `Plugin` type) and `@learncard/init` (for initializing LearnCard):

{% tabs %}
{% tab title="Bun" %}

```bash
bun add @learncard/core @learncard/init
```

{% endtab %}

{% tab title="yarn" %}

```bash
yarn add @learncard/core @learncard/init
```

{% endtab %}

{% tab title="npm" %}

```bash
npm i @learncard/core @learncard/init
```

{% endtab %}
{% endtabs %}

### Create the Types

To ease plugin development, it's best to start by defining the interface for your plugin. This can be done quite easily using [the `Plugin` type](/sdks/learncard-core/writing-plugins#the-plugin-type):

{% code title="src/types.ts" lineNumbers="true" %}

```typescript
import { Plugin } from '@learncard/core';

export type MyPluginMethods = {
    getFavoriteNumber: () => number;
};

export type MyPluginType = Plugin<'MyPluginName', any, MyPluginMethods>;
```

{% endcode %}

The preceding file defines a plugin named `MyPluginName` that exposes one method: `getFavoriteNumber`

### Create the Plugin

{% code title="src/index.ts" lineNumbers="true" %}

```typescript
import { MyPluginType } from './types';

export const MyPlugin: MyPluginType = {
    name: 'MyPluginName',
    methods: { getFavoriteNumber: () => 4 },
};
```

{% endcode %}

### Create a Test for Your Plugin

It's important to write tests for your plugins, so others can rely on them :thumbsup:

{% tabs %}
{% tab title="Bun" %}

```bash
bun add --dev jest @types/jest
```

{% endtab %}

{% tab title="yarn" %}

```bash
yarn add jest @types/jest --dev
```

{% endtab %}

{% tab title="npm" %}

```bash
npm i --save-dev jest @types/jest
```

{% endtab %}
{% endtabs %}

Then, write your test:

{% code title="test/index.test.ts" lineNumbers="true" %}

```typescript
import { initLearnCard } from '@learncard/init';
import { MyPlugin } from '../src/index';

describe('MyPlugin', () => {
    it('should return my favorite number', async () => {
        const learnCard = await initLearnCard();
        const learnCardWithMyPlugin = await learnCard.addPlugin(MyPlugin);

        const favoriteNumber = learnCardWithMyPlugin.invoke.getFavoriteNumber();

        expect(favoriteNumber).toBe(4);
    });
});
```

{% endcode %}

If all looks good, you should be able to `bun test` and successfully pass the test:

<img src="/files/yqMccKvWfLJ2ddkqpXdr" alt="" data-size="original">

**That's it—you've got a simple plugin! 🎉**

Now you can add it to a LearnCard object:

```typescript
import { initLearnCard } from '@learncard/init';

const learnCard = await initLearnCard();
const learnCardWithMyPlugin = await learnCard.addPlugin(MyPlugin);

console.log(learnCardWithMyPlugin.invoke.getFavoriteNumber()); // 4
```

### Publish Your Plugin to NPM

If you don't have anything secret contained in your plugin, you are encouraged to publish it as a package to NPM and share it with the world 🏆.

Let's walk through how to do that together:

## Make an npm account

If you haven't yet, [follow these short steps to create an npm account](https://docs.npmjs.com/creating-a-new-npm-user-account). You will need to come up with a username, email, and password!

## Create the package boilerplate

As noted in our docs on [The Simplest Plugin](#boilerplate), if you've never set up a TS/node package before, we greatly recommend using [aqu](https://www.npmjs.com/package/aqu)!

{% tabs %}
{% tab title="Bun" %}

<pre class="language-bash"><code class="lang-bash"><strong>bunx aqu create learn-card-example-plugin
</strong><strong>:
</strong>? Pick package manager: bun
? Specify package description: () # Describe your plugin!
? Package author: # Who are you?
? Git repository (only for package.json information): 
? Pick license: MIT # See https://choosealicense.com/
<strong>? Pick template: typescript
</strong>
cd learn-card-example-plugin
</code></pre>

{% endtab %}

{% tab title="yarn" %}

```bash
yarn dlx aqu create learn-card-example-plugin

? Pick package manager: yarn
? Specify package description: () # Describe your plugin!
? Package author: # Who are you?
? Git repository (only for package.json information):
? Pick license: MIT # See https://choosealicense.com/
? Pick template: typescript

cd learn-card-example-plugin
```

{% endtab %}

{% tab title="npm" %}

```bash
npx aqu create learn-card-example-plugin

? Pick package manager: npm
? Specify package description: () # Describe your plugin!
? Package author: # Who are you?
? Git repository (only for package.json information):
? Pick license: MIT # See https://choosealicense.com/
? Pick template: typescript

cd learn-card-example-plugin
```

{% endtab %}
{% endtabs %}

## Create a Github Repo

If you've selected an open source license (such as MIT or ISC), please make a Github Repo containing the code to your plugin! If you've never done this before, we recommend using the [Github CLI](https://cli.github.com/).

First, create a [Github Account](https://github.com/join), then install and login with the CLI. This is usually done with the following command:

```bash
gh auth login
```

After getting all setup, initialize and create the repo with the following commands:

```bash
git init

echo "node_modules/" >> .gitignore
echo "dist/" >> .gitignore

git add .
git commit -m "Initial Commit"

gh repo create
? What would you like to do? Push an existing local repository to GitHub
? Path to local repository .
? Repository name learn-card-example-plugin
? Description Example LearnCard Plugin!
? Visibility Public
✓ Created repository {REPOSITORY_NAME} on GitHub
? Add a remote? Yes
? What should the new remote be called? origin
✓ Added remote {REPOSITORY_URL}
? Would you like to push commits from the current branch to "origin"? Yes
✓ Pushed commits to {REPOSITORY_URL}
```

After getting a repo up, it's a good idea to add the URL (shown above as `{REPOSITORY_URL}`) to the `package.json`!

{% code title="package.json" %}

```json
  "repository": {
    "type": "git",
    "url": {REPOSITORY_URL}
  },
```

{% endcode %}

## Release the Package

With everything set up, you may run the release command!

{% tabs %}
{% tab title="Bun" %}

```bash
bun run release
```

{% endtab %}

{% tab title="yarn" %}

```bash
yarn release
```

{% endtab %}

{% tab title="npm" %}

```bash
npm run release
```

{% endtab %}
{% endtabs %}

If you didn't use aqu to create your package, you may need to use the `publish` command directly:

{% tabs %}
{% tab title="Bun" %}

```bash
bun publish
```

{% endtab %}

{% tab title="yarn" %}

```bash
yarn publish
```

{% endtab %}

{% tab title="npm" %}

```bash
npm publish
```

{% endtab %}
{% endtabs %}

Congratulations! 🥳 Your plugin is officially published and others may use it by installing it from npm!

### Next Steps

For more info on adding plugins to a LearnCard:

{% content-ref url="/pages/WjJbZyLJ0l7ZwUJhhS0m" %}
[Plugin System](/core-concepts/architecture-and-principles/plugins)
{% endcontent-ref %}

For more info on constructing the LearnCard object:

{% content-ref url="/pages/4umV73ZRVcfyu5TTw8Na" %}
[Usage Examples](/sdks/learncard-core/construction)
{% endcontent-ref %}


# Create a Credential

Tutorial: Create and Send Your First Digital Credential

Welcome! This tutorial will walk you through creating your very first digital Verifiable Credential (VC) and sending it to your LearnCard app. Think of a VC as a secure, digital certificate or badge that can prove something, like an achievement or a skill.

{% embed url="<https://www.figma.com/board/DPGBfPLlss2K6KmDLCN3ul/LearnCard-Docs?node-id=131-661&p=f&t=fk1wywzjUFmakXJE-0>" %}

## **What you'll accomplish:**

1. Set up a simple "Issuer" environment using the LearnCard SDK.
2. Design and create a "Workshop Completion" Verifiable Credential.
3. Digitally sign (issue) the credential to make it official.
4. Send this credential to your own LearnCard app using your Profile ID.
5. View the received credential in your LearnCard app.

**Why is this useful?** Understanding this basic flow is the first step to building applications that can issue digital badges, certificates, or any other kind of verifiable proof to users, empowering them with portable and trustworthy records.

{% embed url="<https://codepen.io/Jacks-n-Smith/pen/YPPgyyM>" fullWidth="false" %}

## **Prerequisites:**

1. **Node Installed:** Node.js installed on your computer.
2. **Basic Understanding:** While this is a beginner tutorial, a quick read of our [What is a Verifiable Credential?](/core-concepts/credentials-and-data/verifiable-credentials-vcs) and [What is a DID?](/core-concepts/identities-and-keys/decentralized-identifiers-dids) Core Concept pages will be helpful.

***

## Part 0: Project Setup

{% tabs %}
{% tab title="TypeScript" %}

```bash
# 1. Create a new directory and navigate into it
mkdir learncard-tutorial-1
cd learncard-tutorial-1

# 2. Initialize a Node.js project
npm init -y

# 3. Install LearnCard and the necessary tools for this tutorial
npm install @learncard/init @learncard/core @learncard/types dotenv 
npm install --save-dev typescript tsx @types/node

# 4. Create a TypeScript configuration file
npx tsc --init --rootDir ./ --outDir ./dist --esModuleInterop --resolveJsonModule --lib es2022 --module esnext  --moduleResolution node
```

{% endtab %}

{% tab title="Javascript" %}

```bash
# 1. Create a new directory and navigate into it
mkdir learncard-tutorial-js-1
cd learncard-tutorial-js-1

# 2. Initialize a Node.js project
npm init -y

# 3. Install LearnCard and the necessary tools for this tutorial
npm install @learncard/init @learncard/core dotenv 
```

Open the `package.json` file that was created in your `learncard-tutorial-js` directory and add the following line:

```json
{
  "name": "learncard-tutorial-js-1",
  "version": "1.0.0",
  // ... other fields ...
  "type": "module"  // <--- Add this line
}
```

{% endtab %}
{% endtabs %}

## Part 1: Setting Up Your Issuer Environment

For this tutorial, your computer will act as the "Issuer" – the entity creating and sending the credential.

### **Step 1.1: Create an Issuer Script**

{% tabs %}
{% tab title="TypeScript" %}
Create a new file in your project folder: `issueCredential.ts`
{% endtab %}

{% tab title="Javascript" %}
Create a new file in your project folder: `issueCredential.js`
{% endtab %}
{% endtabs %}

### **Step 1.2: Initialize LearnCard SDK for the Issuer**

This instance will represent your workshop organization.

{% tabs %}
{% tab title="TypeScript" %}
{% code title="issueCredential.ts" %}

```typescript
import "dotenv/config";

import { NetworkLearnCardFromSeed, initLearnCard } from "@learncard/init";
import { UnsignedVC, VC, LCNProfile } from "@learncard/types";

async function setupIssuerLearnCard() {
  const issuerSeed = process.env.SECURE_SEED;

  if (!issuerSeed) {
    throw new Error(
      "Can not initialize LearnCard without a secure seed. Please create an .env file with SECURE_SEED set as a 64-digit string."
    );
  }

  const learnCardIssuer: NetworkLearnCardFromSeed['returnValue'] = await initLearnCard({
    seed: issuerSeed, // This generates the Issuer's DID and keys
    network: true, // We need network capabilities to send the credential
    allowRemoteContexts: true, // We will issue a credential with a remote context
  });

  console.log("Issuer LearnCard Initialized.");
  console.log("Issuer DID:", learnCardIssuer.id.did());
  return learnCardIssuer;
}
// (We'll call this function later)
```

{% endcode %}
{% endtab %}

{% tab title="Javascript" %}
{% code title="issueCredential.js" %}

```typescript
import "dotenv/config";

import { initLearnCard } from "@learncard/init";

async function setupIssuerLearnCard() {
  const issuerSeed = process.env.SECURE_SEED;

  if (!issuerSeed) {
    throw new Error(
      "Can not initialize LearnCard without a secure seed. Please create an .env file with SECURE_SEED set as a 64-digit string."
    );
  }

  const learnCardIssuer = await initLearnCard({
    seed: issuerSeed, // This generates the Issuer's DID and keys
    network: true, // We need network capabilities to send the credential
    allowRemoteContexts: true, // We will issue a credential with a remote context
  });

  console.log("Issuer LearnCard Initialized.");
  console.log("Issuer DID:", learnCardIssuer.id.did());
  return learnCardIssuer;
}
// (We'll call this function later)
```

{% endcode %}
{% endtab %}
{% endtabs %}

This code initializes a LearnCard instance.

{% hint style="info" %}
The `seed` is used to generate a unique Decentralized Identifier (DID) and cryptographic keys for your Issuer. In a real application, this seed must be kept extremely secure. [Learn more](/core-concepts/identities-and-keys/seed-phrases).
{% endhint %}

### **Step 1.3: Ensure Issuer Has a Service Profile**

To interact with the LearnCard Network effectively (like sending credentials), your Issuer's DID should be associated with a Service Profile.

{% tabs %}
{% tab title="TypeScript" %}
{% code title="Add this function to issueCredential.ts" %}

```typescript
async function ensureIssuerProfile(learnCardIssuer: NetworkLearnCardFromSeed['returnValue']) {
  const issuerServiceProfileData: Omit<LCNProfile, 'did' | 'isServiceProfile'> = {
    profileId: process.env.PROFILE_ID!,
    displayName: process.env.PROFILE_NAME!,
    bio: '',
    shortBio: '',
    // Add other relevant details for your issuer profile
  };

  if (!issuerServiceProfileData.profileId) {
    throw new Error(
      "Please create an .env file with PROFILE_ID set as a unique, 3-40 character string. e.g: my-organization-id."
    );
  }
  if (!issuerServiceProfileData.displayName) {
    throw new Error(
      'Please create an .env file with PROFILE_NAME set as human readable string, e.g: "My Organization".'
    );
  }

  try {
    // Check if profile exists first, to avoid errors if run multiple times
    let profile = await learnCardIssuer.invoke.getProfile(
      issuerServiceProfileData.profileId
    );
    if (!profile) {
      console.log(
        `Creating service profile for issuer: ${issuerServiceProfileData.profileId}`
      );
      await learnCardIssuer.invoke.createServiceProfile(
        issuerServiceProfileData
      );
      console.log("Issuer Service Profile created successfully.");
    } else {
      console.log("Issuer Service Profile already exists.");
    }
  } catch (error: any) {
    console.error("Error ensuring issuer profile:", error.message);
  }
}
// (We'll call this after setupIssuerLearnCard)
```

{% endcode %}
{% endtab %}

{% tab title="Javascript" %}
{% code title="Add this function to issueCredential.js" %}

```typescript
async function ensureIssuerProfile(learnCardIssuer) {
  const issuerServiceProfileData = {
    profileId: process.env.PROFILE_ID,
    displayName: process.env.PROFILE_NAME,
  };

  if (!issuerServiceProfileData.profileId) {
    throw new Error(
      "Please create an .env file with PROFILE_ID set as a unique, 3-40 character string. e.g: my-organization-id."
    );
  }
  if (!issuerServiceProfileData.displayName) {
    throw new Error(
      'Please create an .env file with PROFILE_NAME set as human readable string, e.g: "My Organization".'
    );
  }

  try {
    let profile = await learnCardIssuer.invoke.getProfile(
      issuerServiceProfileData.profileId
    );
    if (!profile) {
      console.log(
        `Creating service profile for issuer: ${issuerServiceProfileData.profileId}`
      );
      await learnCardIssuer.invoke.createServiceProfile(
        issuerServiceProfileData
      );
      console.log("Issuer Service Profile created successfully.");
    } else {
      console.log("Issuer Service Profile already exists.");
    }
  } catch (error) {
    console.error("Error ensuring issuer profile:", error.message);
  }
}

```

{% endcode %}
{% endtab %}
{% endtabs %}

### **Step 1.4: Generate Secure Seed and .env file**

#### Create and save your seed to .en&#x76;*:*

{% tabs %}
{% tab title="macOS / Linux" %}
Run the following command in your terminal:

{% code overflow="wrap" %}

```bash
echo "SECURE_SEED=\"$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")\"" > .env
```

{% endcode %}
{% endtab %}

{% tab title="Windows Cmd" %}
Run the following command in your Windows cmd prompt:

{% code overflow="wrap" %}

```bash
echo "SECURE_SEED=\"$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")\"" > .env
```

{% endcode %}
{% endtab %}

{% tab title="Powershell" %}
Run the following command in Powershell:

{% code overflow="wrap" %}

```bash
"SECURE_SEED=\"$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")\"" | Out-File -Encoding utf8 .env
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Add config variables to your `.env`:

{% hint style="info" %}
You must create a unique profile ID for your organization. It must be 3-40 characters, lowercase, no spaces or special characters. E.g.: `my-organization`, `acme`, `taffy-co-organization` , etc.
{% endhint %}

{% code title=".env" overflow="wrap" %}

```bash
SECURE_SEED="..." # Created from command in prior step.
PROFILE_ID="<unique-profile-id>" # Unique profile ID.
PROFILE_NAME="<Display Name>" # Human Readable Display Name
```

{% endcode %}

***

## Part 2: Designing Your "Workshop Completion" Credential

Now, let's define what information our "Workshop Completion" credential will hold.

### Step 2.1: Retrieve Your LearnCard Profile ID

[Login or Signup for LearnCard App](https://learncard.app/), and grab your unique Profile ID:

{% embed url="<https://www.loom.com/share/79905a8d12e14f4c9da5e6929f187445?sid=954f288f-87b6-4986-846b-08e1238964d4>" %}

{% hint style="info" %}

#### **How to Find:**

Open your [LearnCard app](https://learncard.app/), navigate to your profile section by clicking it in the upper right corner. Click "My Account." Copy the Profile ID accurately; it's case-sensitive and usually looks something like `@your-chosen-profile-id` or a longer unique string.
{% endhint %}

### **Step 2.1: Define the Credential Content**

A Verifiable Credential is a set of claims made by an Issuer about a Subject (the recipient).

{% tabs %}
{% tab title="TypeScript" %}
{% code title="// Add this to issueCredential.ts" %}

```typescript
// IMPORTANT: Replace with the Profile ID you got from YOUR LearnCard App
const recipientProfileId = 'YOUR_LEARNCARD_APP_PROFILE_ID'; 

async function generateWorkshopCredentialForRecipient(
  learnCardIssuer: NetworkLearnCardFromSeed['returnValue'],
  recipientProfileId: string
): Promise<UnsignedVC> {
  // Retrieve recipient profile to retrieve their DID
  const recipientProfile = await learnCardIssuer.invoke.getProfile(
    recipientProfileId
  );
  if (!recipientProfile) {
    throw new Error(
      "Recipient LearnCard Profile ID does not exist in LearnCloud Network."
    );
  }

  // This will also be the credentialSubject.id if the credential is about the recipient directly.
  const recipientDidForCredential = recipientProfile.did;

  const workshopCredentialContent: UnsignedVC = {
    // "@context" defines the vocabulary used (like a dictionary for terms)
    "@context": [
      "https://www.w3.org/2018/credentials/v1",
      "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.1.json",
      "https://ctx.learncard.com/boosts/1.0.3.json",
    ],
    // "type" specifies what kind of credential this is
    type: ["VerifiableCredential", "OpenBadgeCredential", "BoostCredential"], // Standard VC type + OpenBadge type + Boost type
    issuanceDate: new Date().toISOString(), // Today's date
    issuer: learnCardIssuer.id.did(),
    name: "LearnCard Basics Workshop",
    // "credentialSubject" is about whom or what the credential is
    credentialSubject: {
      achievement: {
        achievementType: "Badge",
        criteria: {
          narrative:
            "Awarded for successfully completing the interactive LearnCard tutorial.",
        },
        description:
          "This badge was generated in the CodePen demonstration project in the LearnCard Developer Docs.",
        id: "urn:uuid:" + crypto.randomUUID(), // Generate a unique ID
        image: "https://example.com/badge-images/teamwork.png",
        name: "LearnCard Basics Workshop",
        type: ["Achievement"],
      },
      id: recipientDidForCredential, // The DID of the person who completed the workshop
      type: ["AchievementSubject"],
    },
    // Additional Boost Display Fields for Extra Customization
    display: {
      backgroundColor: "#40cba6",
      displayType: "badge",
    },
    image: "https://cdn.filestackcontent.com/YjQDRvq6RzaYANcAxKWE",
    // "proof" will be added automatically when the credential is signed
  };

  return workshopCredentialContent;
}
```

{% endcode %}
{% endtab %}

{% tab title="Javascript" %}
{% code title="// Add this to issueCredential.js" %}

```typescript
// IMPORTANT: Replace with the Profile ID you got from YOUR LearnCard App
const recipientProfileId = 'YOUR_LEARNCARD_APP_PROFILE_ID'; 

async function generateWorkshopCredentialForRecipient(
  learnCardIssuer: NetworkLearnCardFromSeed,
  recipientProfileId: string
): Promise<UnsignedVC> {
  // Retrieve recipient profile to retrieve their DID
  const recipientProfile = await learnCardIssuer.invoke.getProfile(
    recipientProfileId
  );
  if (!recipientProfile) {
    throw new Error(
      "Recipient LearnCard Profile ID does not exist in LearnCloud Network."
    );
  }

  // This will also be the credentialSubject.id if the credential is about the recipient directly.
  const recipientDidForCredential = recipientProfile.did;

  const workshopCredentialContent: UnsignedVC = {
    // "@context" defines the vocabulary used (like a dictionary for terms)
    "@context": [
      "https://www.w3.org/2018/credentials/v1",
      "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.1.json",
      "https://ctx.learncard.com/boosts/1.0.3.json",
    ],
    // "type" specifies what kind of credential this is
    type: ["VerifiableCredential", "OpenBadgeCredential", "BoostCredential"], // Standard VC type + OpenBadge type + Boost type
    issuanceDate: new Date().toISOString(), // Today's date
    issuer: learnCardIssuer.id.did(),
    name: "LearnCard Basics Workshop",
    // "credentialSubject" is about whom or what the credential is
    credentialSubject: {
      achievement: {
        achievementType: "Badge",
        criteria: {
          narrative:
            "Awarded for successfully completing the interactive LearnCard tutorial.",
        },
        description:
          "This badge was generated in the CodePen demonstration project in the LearnCard Developer Docs.",
        id: "urn:uuid:" + crypto.randomUUID(), // Generate a unique ID
        image: "https://example.com/badge-images/teamwork.png",
        name: "LearnCard Basics Workshop",
        type: ["Achievement"],
      },
      id: recipientDidForCredential, // The DID of the person who completed the workshop
      type: ["AchievementSubject"],
    },
    // Additional Boost Display Fields for Extra Customization
    display: {
      backgroundColor: "#40cba6",
      displayType: "badge",
    },
    image: "https://cdn.filestackcontent.com/YjQDRvq6RzaYANcAxKWE",
    // "proof" will be added automatically when the credential is signed
  };

  return workshopCredentialContent;
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

{% hint style="success" %}
✨ **Key Points:**

* **`@context`**: Tells systems how to interpret the fields.
* **`type`**: Helps categorize the credential. `VerifiableCredential` is standard.
* **`credentialSubject`**: This is the core information. The `id` here should be the DID of the person receiving the credential. For this tutorial, we're using the `recipientProfileId` (which you got from your app) to construct a DID.

Learn more about schemas in our[ Credentials and Data section of our Core Concepts](/core-concepts/credentials-and-data).
{% endhint %}

***

## Part 3: Creating and Signing the Credential (Issuance)

Let's take the content and make it an official, signed Verifiable Credential.

### **Step 3.1: "Issue" / "Sign" the Unsigned Credential**

The LearnCard SDK helps you with this:

{% tabs %}
{% tab title="TypeScript" %}
{% code title="// Add this function to issueCredential.ts" %}

```typescript
async function createAndSignCredential(
  learnCardIssuer: NetworkLearnCardFromSeed['returnValue'],
  unsignedVc: UnsignedVC
) {
  console.log("Unsigned VC:", JSON.stringify(unsignedVc, null, 2));

  console.log("Now signing (issuing) the credential...");
  const signedVc = await learnCardIssuer.invoke.issueCredential(unsignedVc);
  console.log("Signed VC created successfully!");
  console.log(JSON.stringify(signedVc, null, 2));
  return signedVc;
}

// (We'll call this later)
```

{% endcode %}
{% endtab %}

{% tab title="Javascript" %}
{% code title="// Add this function to issueCredential.js" %}

```typescript
async function createAndSignCredential(learnCardIssuer, unsignedVc) {
  console.log("Unsigned VC:", JSON.stringify(unsignedVc, null, 2));

  console.log("Now signing (issuing) the credential...");
  const signedVc = await learnCardIssuer.invoke.issueCredential(unsignedVc);
  console.log("Signed VC created successfully!");
  console.log(JSON.stringify(signedVc, null, 2));
  return signedVc;
}

// (We'll call this later)
```

{% endcode %}
{% endtab %}
{% endtabs %}

{% hint style="info" %}
`issueCredential` adds the issuer's DID, issuance date, and a cryptographic signature, making it verifiable.
{% endhint %}

***

## Part 4: Sending the Credential to Your LearnCard App

Now, let's send this official credential to your LearnCard app.

### **Step 4.1: Use `sendCredential`**

This function from the LearnCard SDK (via the Network plugin) handles the delivery.

{% tabs %}
{% tab title="TypeScript" %}
{% code title="// Add this function to issueCredential.ts" %}

```typescript
async function sendVcToRecipient(
  learnCardIssuer: NetworkLearnCardFromSeed['returnValue'],
  recipientLcnProfileId: string,
  signedVc: VC
) {
  console.log(`Sending credential to Profile ID: ${recipientLcnProfileId}`);
  try {
    const sentCredentialUri = await learnCardIssuer.invoke.sendCredential(
      recipientLcnProfileId,
      signedVc,
    );
    console.log(
      "Credential sent successfully! Sent Credential URI:",
      sentCredentialUri
    );
    return sentCredentialUri;
  } catch (error) {
    console.error("Error sending credential:", error);
    throw error;
  }
}

// (We'll call this later)
```

{% endcode %}
{% endtab %}

{% tab title="Javascript" %}
{% code title="// Add this function to issueCredential.js" %}

```javascript
async function sendVcToRecipient(
  learnCardIssuer,
  recipientLcnProfileId,
  signedVc
) {
  console.log(`Sending credential to Profile ID: ${recipientLcnProfileId}`);
  try {
    const sentCredentialUri = await learnCardIssuer.invoke.sendCredential(
      recipientLcnProfileId,
      signedVc
    );
    console.log(
      "Credential sent successfully! Sent Credential URI:",
      sentCredentialUri
    );
    return sentCredentialUri;
  } catch (error) {
    console.error("Error sending credential:", error);
    throw error;
  }
}

```

{% endcode %}
{% endtab %}
{% endtabs %}

***

## Part 5: Putting It All Together & Viewing in Your App

Let's create a main function to run these steps.

### **Step 5.1: Main Script Logic**

{% tabs %}
{% tab title="TypeScript" %}
{% code title="// Add this main execution block at the end of issueCredential.ts" %}

```typescript
async function main() {
  // @ts-ignore
  if (recipientProfileId === "YOUR_LEARNCARD_APP_PROFILE_ID") {
    console.error(
      "Please replace 'YOUR_LEARNCARD_APP_PROFILE_ID' with your actual Profile ID from the LearnCard app in the 'recipientProfileId' variable."
    );
    return;
  }

  const learnCardIssuer = await setupIssuerLearnCard();
  await ensureIssuerProfile(learnCardIssuer);
  const workshopCredential = await generateWorkshopCredentialForRecipient(
    learnCardIssuer,
    recipientProfileId
  );

  const signedVc = await createAndSignCredential(
    learnCardIssuer,
    workshopCredential
  );

  if (signedVc) {
    await sendVcToRecipient(learnCardIssuer, recipientProfileId, signedVc);
    console.log(
      "\nTutorial complete! Check your LearnCard app for the new credential."
    );
    console.log("It might take a moment to receive a notification.");
  } else {
    console.log("Credential creation or signing failed. Cannot send.");
  }
}

main().catch((err) => console.error("Tutorial encountered an error:", err));
```

{% endcode %}
{% endtab %}

{% tab title="Javascript" %}
{% code title="// Add this main execution block at the end of issueCredential.js" %}

```typescript
async function main() {

  if (recipientProfileId === "YOUR_LEARNCARD_APP_PROFILE_ID") {
    console.error(
      "Please replace 'YOUR_LEARNCARD_APP_PROFILE_ID' with your actual Profile ID from the LearnCard app in the 'recipientProfileId' variable."
    );
    return;
  }

  const learnCardIssuer = await setupIssuerLearnCard();
  await ensureIssuerProfile(learnCardIssuer);
  const workshopCredential = await generateWorkshopCredentialForRecipient(
    learnCardIssuer,
    recipientProfileId
  );

  const signedVc = await createAndSignCredential(
    learnCardIssuer,
    workshopCredential
  );

  if (signedVc) {
    await sendVcToRecipient(learnCardIssuer, recipientProfileId, signedVc);
    console.log(
      "\nTutorial complete! Check your LearnCard app for the new credential."
    );
    console.log("It might take a moment to receive a notification.");
  } else {
    console.log("Credential creation or signing failed. Cannot send.");
  }
}

main().catch((err) => console.error("Tutorial encountered an error:", err));
```

{% endcode %}
{% endtab %}
{% endtabs %}

### **Step 5.2: Run Your Script**

{% tabs %}
{% tab title="TypeScript" %}

1. **Replace Placeholders:**
   * [ ] In `issueCredential.ts`, find `YOUR_LEARNCARD_APP_PROFILE_ID` and replace it with the Profile ID you copied from your LearnCard app.
   * [ ] Ensure `.env` file contains `SECURE_SEED`, `PROFILE_ID`, and `PROFILE_NAME` with your own unique values set,
2. Save the file.
3. Open your terminal in your project directory and run:
   * `npx tsx issueCredential.ts`
     {% endtab %}

{% tab title="Javascript" %}

1. **Replace Placeholders:**
   * [ ] In `issueCredential.js`, find `YOUR_LEARNCARD_APP_PROFILE_ID` and replace it with the Profile ID you copied from your LearnCard app.
   * [ ] Ensure `.env` file contains `SECURE_SEED`, `PROFILE_ID`, and `PROFILE_NAME` with your own unique values set,
2. Save the file.
3. Open your terminal in your project directory and run:
   * `node issueCredential.js`
     {% endtab %}
     {% endtabs %}

### **Step 5.3: View in Your LearnCard App**

After the script runs successfully, [open your LearnCard app on your device](https://learncard.app/notifications). You should see the new "Workshop Completion Certificate" appear! It might take a few moments for you to get the notification.

<figure><img src="/files/U7q0kSAOMHgLNJsQu5JB" alt=""><figcaption><p>Head to the <a href="https://learncard.app/notifications">"Alerts" section in LearnCard app</a> to claim the credential you just sent! Ensure you are signed in as the recipient Profile ID.</p></figcaption></figure>

***

## Summary & What's Next

Congratulations! You've successfully: ✅ Set up a basic Issuer using the LearnCard SDK. ✅ Defined, created, and digitally signed a Verifiable Credential. ✅ Sent that credential to your own LearnCard app.

This tutorial covers the fundamental flow of issuing credentials. From here, you can explore:

* Creating more complex credentials with different [**Schemas and Types**](/core-concepts/credentials-and-data/achievement-types-and-categories).
* Using [**ConsentFlows**](/core-concepts/consent-and-permissions/consentflow-overview) to manage data sharing before issuing credentials.
* Integrating this issuance logic into your own applications and backend services.

Explore the rest of our documentation to learn more about the powerful features of LearnCard!


# Create a Boost

Welcome! This tutorial will guide you through creating a "Boost" with LearnCard. Boosts are an enhanced way to manage and issue Verifiable Credentials (VCs), especially when you want to issue a similar credential to multiple people or manage it as a distinct "template."

## **What is a Boost, and Why Use It?**

You already know how to Create and Send a Single Credential. That's great for one-off situations!

A **Boost** takes this a step further. Think of a Boost as a **master template or a blueprint for a specific type of credential**.

* **Direct VC:** Like handwriting a single invitation.
* **Boost:** Like designing a beautiful invitation template that you can then print and send to many guests. Each guest gets a personalized copy (their own VC instance), but they all originate from your master "Boost" template.

**You should use a Boost instead of sending VCs directly when you want to:**

* **Issue the same type of credential to multiple people:** e.g., a "Course Completion" certificate for all students who pass.
* **Track recipients centrally:** Easily see a list of everyone who has received a credential derived from a specific Boost.
* **Manage a credential type:** Update metadata or display properties of the Boost, which can influence future issuances.
* **Delegate issuance:** Grant permissions to others (e.g., "Coaches") to issue credentials based on a Boost you created.
* **Organize credentials:** Create hierarchies of Boosts (e.g., a "Programming" Boost with child Boosts for "Python" and "JavaScript").

*(See the diagrams below for a visual comparison of sending VCs directly vs. sending Boosts)*

**Diagram 1: Sending Individual VCs** *(This is what the previous* [*"Create a Credential" tutorial* ](/tutorials/create-a-credential)*covered)*

```mermaid
graph LR
    subgraph Issuer Ops
        Urmila["Urmila (Issuer Profile)"]
    end

    subgraph Credentials
        VC1["Book Club ID<br/>for Ted<br/>(Credential)"]
        VC2["Course Completion<br/>for Juniper<br/>(Credential)"]
    end

    subgraph Recipients
        Ted["Ted (Recipient Profile)"]
        Juniper["Juniper (Recipient Profile)"]
    end

    Urmila -- "Issues & Sends VC1" --> VC1
    VC1 -- "CREDENTIAL_RECEIVED" --> Ted

    Urmila -- "Issues & Sends VC2" --> VC2
    VC2 -. "Sent, maybe not claimed" .-> Juniper

    linkStyle 0 stroke-width:2px,fill:none,stroke:green;
    linkStyle 1 stroke-width:2px,fill:none,stroke:blue;
    linkStyle 2 stroke-width:2px,fill:none,stroke:green;
    linkStyle 3 stroke-width:2px,fill:none,stroke:blue,stroke-dasharray: 5 5;

```

{% hint style="info" %}
In this model, each credential is a distinct, standalone item.
{% endhint %}

**Diagram 2: Sending Credentials via a Boost** *(This is what this tutorial will cover)*

```mermaid
graph LR
    subgraph Issuer Ops
        UrmilaB["Urmila (Issuer Profile)"]
    end

    subgraph BoostDefinition [" "]
        style BoostDefinition fill:#f0f0f0,stroke:#ccc
        MasterBoost["'Book Club ID'<br/>BOOST<br/>(Template)"]
    end

    subgraph CredentialInstances [" "]
      style CredentialInstances fill:#f9f9f9,stroke:#ddd
        VC_Ted["Book Club ID<br/>for Ted<br/>(Credential Instance)"]
        VC_Juniper["Book Club ID<br/>for Juniper<br/>(Credential Instance)"]
    end

    subgraph Recipients
        TedB["Ted (Recipient Profile)"]
        JuniperB["Juniper (Recipient Profile)"]
    end

    UrmilaB -- "CREATED_BY" --> MasterBoost

    MasterBoost -- "INSTANCE_OF" --> VC_Ted
    UrmilaB -- "Issues & Sends instance" --> VC_Ted
    VC_Ted -- "CREDENTIAL_RECEIVED" --> TedB

    MasterBoost -- "INSTANCE_OF" --> VC_Juniper
    UrmilaB -- "Issues & Sends instance" --> VC_Juniper
    VC_Juniper -. "Sent, maybe not claimed" .-> JuniperB

    linkStyle 0 stroke-width:2px,fill:none,stroke:purple;
    linkStyle 1 stroke-width:2px,fill:none,stroke:orange;
    linkStyle 2 stroke-width:2px,fill:none,stroke:green;
    linkStyle 3 stroke-width:2px,fill:none,stroke:blue;
    linkStyle 4 stroke-width:2px,fill:none,stroke:orange;
    linkStyle 5 stroke-width:2px,fill:none,stroke:green;
    linkStyle 6 stroke-width:2px,fill:none,stroke:blue,stroke-dasharray: 5 5;

```

{% hint style="success" %}
Here, Urmila creates one "Book Club ID" Boost. Then, when she sends it to Ted and Juniper, they each receive a credential that is an *instance of* that main Boost.
{% endhint %}

## **What you'll accomplish in this tutorial:**

1. Set up your Issuer environment (quick recap).
2. Define the content for a "Monthly Tech Meetup Attendee" Boost.
3. Create the Boost on the LearnCard Network.
4. Send this Boost (as individual credentials) to two different recipient Profile IDs.
5. Briefly discuss how you might see who received the Boost.

{% embed url="<https://codepen.io/Jacks-n-Smith/pen/oggVZbj>" fullWidth="false" %}
Full Create and Send a Boost Example
{% endembed %}

## **Prerequisites:**

1. **LearnCard App Installed:** For your recipients (you can use your own Profile ID as one or both recipients for testing).
2. **Recipient Profile IDs:** You'll need at least two unique Profile IDs from LearnCard apps. For testing, you can use your own and perhaps a friend's, or create dummy ones if your test environment allows.
3. **Development Environment:**
   * Node.js installed.
   * A new project folder.
   * LearnCard SDK installed:

     ```bash
     npm install @learncard/init @learncard/didkit-plugin
     # or bun / yarn
     ```
4. **Basic Understanding:** Review [What is a Verifiable Credential?](/core-concepts/credentials-and-data/verifiable-credentials-vcs) and the previous tutorial [Create and Send Your First Digital Credential.](/tutorials/create-a-credential)

***

## Part 1: Setting Up Your Issuer Environment (Recap)

Just like in the previous tutorial, your computer will act as the Issuer.

### **Step 1.1: Create an Issuer Script** Create `issueBoost.ts` (or `.js`).

### **Step 1.2: Initialize LearnCard SDK for the Issuer**

```typescript
// issueBoost.ts
import { initLearnCard } from '@learncard/init';
// import didkit from '@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm'; // If needed

async function setupIssuerLearnCard() {
    const issuerSeed = 'my-tech-meetup-organizer-seed-keep-secret'; // Replace with a unique, secure seed!
    const learnCardIssuer = await initLearnCard({
        seed: issuerSeed,
        network: true,
        allowRemoteContexts: true, // We will issue a credential with a remote context
        // didkit: didkit, // If self-hosting wasm
    });
    console.log('Issuer LearnCard Initialized for Boosts.');
    console.log('Issuer DID:', learnCardIssuer.id.did());
    return learnCardIssuer;
}
```

### **Step 1.3: Ensure Issuer Has a Service Profile**

{% hint style="info" %}
(Same as the previous tutorial – important for network interactions)
{% endhint %}

```typescript
// Add this function to issueBoost.ts
async function ensureIssuerProfile(learnCardIssuer: any) {
    // Use 'any' or the specific LearnCard type
    const issuerServiceProfileData = {
        profileId: 'tech-meetup-hq', // Unique ID for your issuer profile
        displayName: 'Tech Meetup HQ',
    };
    try {
        let profile = await learnCardIssuer.invoke.getProfile(issuerServiceProfileData.profileId);
        if (!profile) {
            console.log(
                `Creating service profile for issuer: ${issuerServiceProfileData.profileId}`
            );
            await learnCardIssuer.invoke.createServiceProfile(issuerServiceProfileData);
            console.log('Issuer Service Profile for Boosts created successfully.');
        } else {
            console.log('Issuer Service Profile for Boosts already exists.');
        }
    } catch (error) {
        console.error('Error ensuring issuer profile for Boosts:', error.message);
    }
}
```

## Part 2: Defining the Credential Content for Your Boost

This is the "template" for the credential that will be issued each time you send this Boost. For our example, it's an attendance credential for a monthly tech meetup.

```typescript
// Add this to issueBoost.ts

// The recipient's DID will be filled in when the Boost is *sent* to each person.
// So, the credentialSubject.id here is often a placeholder or omitted,
// as the `sendBoost` operation will create a specific VC instance for each recipient.
// However, the overall structure of what you're attesting to is defined here.

const meetupAttendeeCredentialTemplate = {
    // "@context" defines the vocabulary used (like a dictionary for terms)
    "@context": [
      "https://www.w3.org/2018/credentials/v1",
      "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.1.json",
      "https://ctx.learncard.com/boosts/1.0.3.json",
    ],
    // "type" specifies what kind of credential this is
    "type": [
      "VerifiableCredential",
      "OpenBadgeCredential",
      "BoostCredential"
    ] // Standard VC type + OpenBadge type + Boost type
    // `issuer` and `issuanceDate` will be added when the Boost is created/sent.
    "issuer": "did:example:issuer",
    "name": "Monthly Tech Innovators Meetup",
     // "credentialSubject" is about whom or what the credential is
    "credentialSubject": {
      "id": "did:example:recipient",
      "achievement": {
        "achievementType": "Badge",
        "criteria": {
          "narrative": "Awarded for attending the Monthly Tech Innovators Meetup."
        },
        "description": "Tech Innovators Meetup is a montly gathering of like-minded tech enthusiasts and entrepreneurs.",
        "id": "urn:uuid:" + crypto.randomUUID(), // Generate a unique ID
        "image": "https://cdn.filestackcontent.com/FtOrbWhiTTKb818btGlu",
        "name": "Monthly Tech Innovators Meetup",
        "type": [
          "Achievement"
        ]
      },
      "type": [
        "AchievementSubject"
      ]
    },
    // Additional Boost Display Fields for Extra Customization
    "display": {
      "backgroundColor": "#40cba6",
      "displayType": "badge"
    },
    "image": "https://cdn.filestackcontent.com/FtOrbWhiTTKb818btGluE",
    // "proof" will be added automatically when the credential is signed
};


```

Notice we don't specify the recipient's DID in the `credentialSubject.id` here. The Boost is the template; the specific recipient details are handled when you *send* the Boost.

## Part 3: Creating the Boost

Now, let's create the Boost itself on the LearnCard Network using the template content.

```typescript
// Add this function to issueBoost.ts

async function createMeetupBoost(learnCardIssuer: any, credentialTemplate: any) {
    const boostMetadata = {
        name: 'Tech Innovators Meetup - May 2025 Attendee',
        description: 'Recognizes attendance at the May 2025 Tech Innovators Meetup.',
        category: 'Social Badge', // Choose a relevant category
        // You can add display properties here too, see Boost Core Concepts
    };

    console.log('Creating Boost with metadata:', boostMetadata);
    try {
        console.log('Using template VC for Boost:', JSON.stringify(credentialTemplate, null, 2));

        const boostUri = await learnCardIssuer.invoke.createBoost(
            credentialTemplate,
            boostMetadata
        );
        console.log('Boost Created Successfully! Boost URI:', boostUri);
        // Save this boostUri! You'll use it to send the Boost to recipients.
        return boostUri;
    } catch (error) {
        console.error('Error creating Boost:', error);
        throw error;
    }
}
```

When you run this, you'll get a `boostUri`. This URI is the identifier for your Boost template.

## Part 4: Sending the Boost to Multiple Recipients

With your `boostUri`, you can now send it to your attendees. Each `sendBoost` call will create a unique Verifiable Credential instance for that recipient, based on the Boost template.

```typescript
// Add this function to issueBoost.ts

async function sendBoostToAttendees(
    learnCardIssuer: any,
    boostUriToShare: string,
    recipientProfileIds: string[]
) {
    if (!boostUriToShare) {
        console.error('Boost URI is missing. Cannot send.');
        return;
    }
    console.log(
        `Preparing to send Boost (${boostUriToShare}) to ${recipientProfileIds.length} recipients.`
    );

    for (const profileId of recipientProfileIds) {
        console.log(`Sending Boost to Profile ID: ${profileId}...`);
        try {
            // `sendBoost` takes the recipient's Profile ID and the Boost URI.
            // It generates a Verifiable Credential instance based on the Boost's template
            // and issues it to the recipient.
            const sentCredentialUri = await learnCardIssuer.invoke.sendBoost(
                profileId,
                boostUriToShare
                // { encrypt: true } // Optional: consider encryption
            );
            console.log(
                `  Successfully sent Boost to ${profileId}. Credential Instance URI: ${sentCredentialUri}`
            );
        } catch (error) {
            console.error(`  Error sending Boost to ${profileId}:`, error.message);
        }
    }
    console.log('Finished sending Boost to all specified recipients.');
}
```

## Part 5: Putting It All Together & Viewing

Let's run the full sequence.

```typescript
// Add this main execution block at the end of issueBoost.ts

async function main() {
    // --- !! REPLACE THESE WITH ACTUAL PROFILE IDs FROM LEARNCARD APPS !! ---
    const attendeeProfileId1 = 'YOUR_FIRST_RECIPIENT_PROFILE_ID';
    const attendeeProfileId2 = 'YOUR_SECOND_RECIPIENT_PROFILE_ID';
    // --- !! --------------------------------------------------------- !! ---

    if (
        attendeeProfileId1 === 'YOUR_FIRST_RECIPIENT_PROFILE_ID' ||
        attendeeProfileId2 === 'YOUR_SECOND_RECIPIENT_PROFILE_ID'
    ) {
        console.error(
            "Please replace 'YOUR_FIRST_RECIPIENT_PROFILE_ID' and 'YOUR_SECOND_RECIPIENT_PROFILE_ID' with actual Profile IDs from LearnCard apps."
        );
        return;
    }
    if (
        await setupIssuerLearnCard().then(
            issuer => issuer.id.did() === 'did:key:z6Mkpissg9N3752fGusN7f5KkHobbWp8rW4xY8y3JYR8XnpZ'
        )
    ) {
        // Default DID for an empty seed, prompt user to change
        console.warn(
            "You are using a default/empty seed for the issuer. Please change 'my-tech-meetup-organizer-seed-keep-secret' to a unique value for a real issuer."
        );
    }

    const learnCardIssuer = await setupIssuerLearnCard();
    await ensureIssuerProfile(learnCardIssuer);

    const meetupBoostUri = await createMeetupBoost(
        learnCardIssuer,
        meetupAttendeeCredentialTemplate
    );

    if (meetupBoostUri) {
        const recipientProfileIds = [attendeeProfileId1, attendeeProfileId2];
        await sendBoostToAttendees(learnCardIssuer, meetupBoostUri, recipientProfileIds);

        console.log('\nBoost Tutorial complete!');
        console.log('Check the LearnCard apps for the recipient Profile IDs you provided.');
        console.log(
            "Each should have received a 'Tech Innovators Meetup - May 2025 Attendee' credential."
        );
        console.log(
            `\nTo see who received this Boost, you could later use: learnCard.invoke.getPaginatedBoostRecipients('${meetupBoostUri}')`
        );
    } else {
        console.log('Boost creation failed. Cannot send to recipients.');
    }
}

main().catch(err => console.error('Boost tutorial encountered an error:', err));
```

### **Step 5.1: Run Your Script**

1. **Replace Placeholders:**
   * In `issueBoost.ts`, find `YOUR_FIRST_RECIPIENT_PROFILE_ID` and `YOUR_SECOND_RECIPIENT_PROFILE_ID` and replace them with actual Profile IDs from LearnCard apps (you can use your own ID twice for testing if needed, but using two different ones is better for demonstration).
   * Also, replace `'my-tech-meetup-organizer-seed-keep-secret'` with your own unique string for the issuer's seed.
2. Save the file.
3. Open your terminal in your project directory and run:
   * If using TypeScript: `npx ts-node issueBoost.ts`
   * If using JavaScript: `node issueBoost.js`

### **Step 5.2: View in LearnCard Apps**

After the script runs, check the LearnCard apps associated with the recipient Profile IDs you used. Each should have received the "Tech Innovators Meetup - May 2025 Attendee" credential in your notifications.

***

## Bonus: Simplified Sending with the `send` Method

If you want to create and send a boost in a single call, you can use the new `send` method. This is ideal for quick issuance scenarios where you don't need to manage the boost template separately.

```typescript
// Create and send a boost in one call
const result = await learnCard.invoke.send({
    type: 'boost',
    recipient: 'recipient-profile-id',
    template: {
        credential: meetupAttendeeCredentialTemplate,
        name: 'Tech Innovators Meetup - May 2025 Attendee',
        category: 'Social Badge',
    },
});

console.log('Credential sent! URI:', result.credentialUri);
console.log('Boost template URI:', result.uri);
```

Or, if you already have a boost template created:

```typescript
// Send using an existing boost template
const result = await learnCard.invoke.send({
    type: 'boost',
    recipient: 'recipient-profile-id',
    templateUri: meetupBoostUri, // The URI from createBoost
});
```

{% hint style="success" %}
The `send` method automatically handles:

* **Signing**: Uses client-side signing if available, falls back to your signing authority
* **Issuance date**: Sets the current timestamp automatically
* **Recipient DID**: Populates the `credentialSubject.id` with the recipient's DID
  {% endhint %}

For more details, see the [Send Credentials How-To Guide](/how-to-guides/send-credentials).

***

## Bonus: Dynamic Templates with Mustache Variables

Want to personalize credentials with unique data for each recipient? Boosts support **Mustache-style templating** that lets you inject dynamic values at issuance time.

### Creating a Templated Boost

Use `{{variableName}}` syntax in your credential template:

```typescript
const templatedCredential = {
    '@context': [
        'https://www.w3.org/2018/credentials/v1',
        'https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.1.json',
    ],
    type: ['VerifiableCredential', 'OpenBadgeCredential'],
    issuer: 'did:web:example.com',
    name: 'Certificate for {{courseName}}',
    credentialSubject: {
        id: 'did:example:recipient',
        type: ['AchievementSubject'],
        achievement: {
            type: ['Achievement'],
            name: '{{courseName}} Completion',
            description:
                'Awarded to {{studentName}} for completing {{courseName}} with grade {{grade}}',
            criteria: { narrative: 'Successfully complete the course' },
        },
    },
};

const boostUri = await learnCard.invoke.createBoost(templatedCredential, {
    name: 'Course Completion Template',
});
```

### Sending with Personalized Data

Provide `templateData` when sending to fill in the variables:

```typescript
const result = await learnCard.invoke.send({
    type: 'boost',
    recipient: 'student-profile-id',
    templateUri: boostUri,
    templateData: {
        courseName: 'Web Development 101',
        studentName: 'Alice Smith',
        grade: 'A',
    },
});
```

The resulting credential will have all placeholders replaced:

* `{{courseName}}` → `Web Development 101`
* `{{studentName}}` → `Alice Smith`
* `{{grade}}` → `A`

{% hint style="info" %}
**Missing Variables**: If you don't provide a value for a variable, it's rendered as an empty string. This is useful for optional fields.
{% endhint %}

### Issuing the Same Template to Multiple Students

```typescript
const students = [
    { profileId: 'alice', name: 'Alice Smith', grade: 'A' },
    { profileId: 'bob', name: 'Bob Johnson', grade: 'B+' },
    { profileId: 'charlie', name: 'Charlie Brown', grade: 'A-' },
];

for (const student of students) {
    await learnCard.invoke.send({
        type: 'boost',
        recipient: student.profileId,
        templateUri: boostUri,
        templateData: {
            courseName: 'Web Development 101',
            studentName: student.name,
            grade: student.grade,
        },
    });
}
```

For more details on dynamic templates, see [Dynamic Templates with Mustache](/core-concepts/credentials-and-data/boost-credentials#dynamic-templates-with-mustache).

***

## Summary & What's Next

Fantastic! You've now learned how to: ✅ Understand the value of Boosts for reusable credential templates. ✅ Define the content for a Boost. ✅ Create a Boost using the LearnCard SDK. ✅ Send instances of that Boost to multiple recipients. ✅ Use the simplified `send` method for quick issuance. ✅ Use dynamic templates with Mustache variables for personalized credentials.

Boosts are a powerful way to manage credentialing at scale. From here, you can explore:

* **Dynamic Templates:** Use Mustache variables for personalized credentials. (See [Dynamic Templates with Mustache](/core-concepts/credentials-and-data/boost-credentials#dynamic-templates-with-mustache)).
* **Retrieving Boost Recipients:** Use `learnCard.invoke.getPaginatedBoostRecipients(boostUri)` to see who has been issued a credential from this Boost.
* **Boost Permissions:** Control who can edit, issue, or manage your Boosts. (See Boost Permission Model).
* **Default Permissions:** Use `defaultPermissions` to create open Boosts that anyone can issue. (See [Default Permissions](/core-concepts/credentials-and-data/boost-credentials#default-permissions)).
* **Boost Hierarchies:** Organize Boosts into parent-child relationships. (See Boost Hierarchies).
* Customizing **Display Options** for your Boosts to make them visually appealing in wallets.
* **ConsentFlow Integration:** Link boosts to consent contracts for automatic routing. (See [ConsentFlow Overview](/core-concepts/consent-and-permissions/consentflow-overview)).

Explore the [Boost Credentials Core Concept page](/core-concepts/credentials-and-data/boost-credentials) for more in-depth information on all the capabilities of Boosts!


# Create a ConsentFlow

Tutorial: Implementing a Basic ConsentFlow

**What is a ConsentFlow?** A ConsentFlow is a powerful mechanism in LearnCard that allows your application or service (as an "Issuer" or "Contract Owner") to request permission from users ("Holders") to access certain parts of their data or to write new information (like credentials) to their profile. It's all based on explicit user consent, ensuring transparency and user control.

## **What you'll accomplish in this tutorial:**

1. Create a ConsentFlow contract defining what data you want to read or write.
2. Generate a URL for users to view and consent to this contract.
3. Simulate placing a consent button on a webpage.
4. Handle the redirect after a user consents to capture their DID (Decentralized Identifier).
5. Read data from the contract for that consenting user.
6. Send a new credential to that user through the contract.

{% embed url="<https://codepen.io/Jacks-n-Smith/pen/azzMQQP>" %}

## **Prerequisites:**

1. **LearnCard SDK Initialized:** You'll need an active `learnCard` instance connected to the network. We'll call it `networkLearnCard`.

   ```typescript
   // Make sure you have the necessary imports
   import { initLearnCard } from '@learncard/init';
   // If using DIDKit, you might need to import the wasm file
   // import didkit from '@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm';

   // Initialize LearnCard (replace with your actual seed and setup)
   const networkLearnCard = await initLearnCard({
       seed: 'your-very-secure-private-hex-seed-for-your-service', // Replace!
       network: true,
       // didkit: didkit, // If you're self-hosting the wasm
   });
   console.log('LearnCard initialized. Your service DID:', networkLearnCard.id.did());
   ```
2. **Service Profile Created:** Your application needs its own profile on the LearnCard Network to act as the owner of the ConsentFlow contract.

   ```typescript
   // Run this once to create your service profile
   const serviceProfileData = {
       displayName: 'My Awesome Learning App',
       profileId: 'my-learning-app', // Unique ID for your service profile
       image: 'https://example.com/app-logo.png',
       // Add other profile fields as needed
   };
   try {
       const serviceDid = await networkLearnCard.invoke.createServiceProfile(serviceProfileData);
       console.log('Service Profile Created/Exists. DID:', serviceDid);
   } catch (e) {
       // Handle error, profileId might already exist, which is fine if it's yours.
       console.warn('Could not create service profile (it might already exist):', e.message);
   }
   ```
3. **Basic Understanding:** Familiarity with [DIDs](/core-concepts/identities-and-keys/decentralized-identifiers-dids) and [Verifiable Credentials (VCs)](/core-concepts/credentials-and-data/verifiable-credentials-vcs) will be helpful.
4. **Web Environment:** You'll need a way to simulate a user clicking a link and your application handling a redirect (e.g., a simple HTML page and some client-side JavaScript for testing).

***

## Part 1: Creating Your ConsentFlow Contract

This contract will define what permissions your application is requesting from users.

### **Step 1.1: Define Your Contract's Terms**

The contract specifies what your app can `read` from a user's profile/wallet and what it can `write` to it, after they consent.

```typescript
const myAppConsentFlowContract = {
    name: 'My Awesome App Data Sharing',
    subtitle: 'Share learning achievements and receive new badges!',
    description:
        'By consenting, you allow My Awesome Learning App to view your completed courses and issue new achievement badges to your profile.',
    image: 'https://example.com/contract-image.png', // Optional: URL for an image representing your contract

    // This is where you define what data your app wants to read or write
    contract: {
        read: {
            // What your app wants to read from the user
            personal: {
                Name: { required: false }, // Requesting to read the user's name, but it's optional for them to share
            },
            credentials: {
                categories: {
                    'Learning History': { required: true }, // Must share credentials in this category
                    'Achievement': { required: false }, // Optionally share these
                },
            },
        },
        write: {
            // What your app wants to write (issue) to the user
            credentials: {
                categories: {
                    'Achievement': { required: true }, // Your app will issue 'Achievement' credentials
                    'ID': { required: false },
                },
            },
        },
    },
    // IMPORTANT: Set a URL on your website to handle the user after they consent
    redirectUrl: 'https://yourapp.com/consent-callback', // User will be sent here with their DID
};
```

{% hint style="info" %}
✨ **Good to know:**

* `read` and `write` permissions are structured by `personal` data fields and `credentials` (grouped by `categories`).
* You can mark items as `required: true` or `required: false`.
* The `redirectUrl` is crucial for getting the user back to your application with their consent information.
* Supported credential categories include: `Achievement`, `ID`, `Learning History`, `Work History`, `Social Badge`, `Membership`, `Accomplishment`, `Accommodation`, `Family`, `Course`.
  {% endhint %}

### **Step 1.2: Create the Contract**

Now, use the LearnCard SDK to publish this contract definition to the LearnCard Network.

```typescript
async function createContract() {
    try {
        const contractUri = await networkLearnCard.invoke.createContract(myAppConsentFlowContract);
        console.log('ConsentFlow Contract Created! URI:', contractUri);
        // Save this contractUri! You'll need it.
        return contractUri;
    } catch (error) {
        console.error('Error creating contract:', error);
        throw error;
    }
}

// Example usage:
// createContract().then(uri => { /* Store and use this URI */ });
```

{% hint style="success" %}
**Action:** Call this function. Keep the `contractUri` safe – it's the unique identifier for your contract.
{% endhint %}

***

## Part 2: Enabling User Consent on Your Website

Now that your contract exists, users need a way to view and consent to it.

### **Step 2.1: Construct the Consent URL**

The LearnCard platform provides a standard URL for users to interact with ConsentFlow contracts.

```typescript
// Assume you have the contractUri from Part 1
const contractUri = 'uri:contract:YOUR_CONTRACT_URI_HERE'; // Replace with your actual contract URI
const userFacingConsentUrl = `https://learncard.app/consent-flow?uri=${encodeURIComponent(
    contractUri
)}`;

// If you want to ensure the user returns to a *specific* page after consenting,
// and that page is different from the contract's main redirectUrl, you can add 'returnTo':
const specificReturnToUrl = 'https://yourapp.com/specific-post-consent-page';
const urlWithSpecificReturn = `https://learncard.app/consent-flow?uri=${encodeURIComponent(
    contractUri
)}&returnTo=${encodeURIComponent(specificReturnToUrl)}`;

console.log('User Consent URL:', userFacingConsentUrl);
// console.log("URL with specific returnTo:", urlWithSpecificReturn);
```

{% hint style="warning" %}
**Important:** The `returnTo` URL *must* be `http://` or `https://`.
{% endhint %}

### **Step 2.2: Add a Consent Button to Your Webpage**

On your website or application, provide a button or link that directs the user to this `userFacingConsentUrl`.

```html
<a id="consentButton" href="#" target="_blank"> Share Learning Data with My Awesome App </a>

<script>
    const contractUriFromBackend = 'uri:contract:YOUR_CONTRACT_URI_HERE'; // Get this from your backend
    const appRedirectPage = 'https://yourapp.com/consent-callback'; // Your page to handle the redirect

    const consentUrl = `https://learncard.app/consent-flow?uri=${encodeURIComponent(
        contractUriFromBackend
    )}&returnTo=${encodeURIComponent(appRedirectPage)}`;

    document.getElementById('consentButton').href = consentUrl;
</script>
```

When a user clicks this, they'll be taken to `learncard.app` to review your contract and give their consent.

***

## Part 3: Handling the Redirect and Capturing the User's DID

After the user consents (or denies) on `learncard.app`, they will be redirected back to the `redirectUrl` you specified in your contract (or the `returnTo` URL in the consent link). The DID of the consenting user will be appended as a query parameter.

### **Step 3.1: Your Redirect Page (`https://yourapp.com/consent-callback`)**

This page in your application needs to be able to read URL query parameters.

### **Step 3.2: Extract the User's DID**

Here's a simple client-side JavaScript example for your redirect page:

```typescript
// In your /consent-callback page's JavaScript
function handleConsentRedirect() {
    const queryParams = new URLSearchParams(window.location.search);
    const userDid = queryParams.get('did');

    if (userDid) {
        console.log('User consented! Their DID is:', userDid);
        // Now you can:
        // 1. Store this userDid in association with your application's user account.
        // 2. Make a backend call to your server with this DID.
        // 3. Use this DID to fetch consented data (see Part 4).
        alert(`Consent received for DID: ${userDid}`);
        // For a real app, you'd likely redirect them to their dashboard or next step.
    } else {
        console.error('Consent redirect did not include a DID, or user denied consent.');
        alert('Consent process was not completed or was denied.');
    }
}

// Call this function when your redirect page loads
window.onload = handleConsentRedirect;
```

Now you have the DID of the user who consented! This is crucial for interacting with the data they agreed to share.

***

## Part 4: Interacting with Consented Data

With the `contractUri` and the `userDid` of the consenter, your service (using its `networkLearnCard` instance) can now act on the consent.

### **Step 4.1: Reading Data Shared by the User**

You can fetch all data shared by users for a specific contract. Then, you can find the record for the specific user using their DID.

```typescript
// (Assuming networkLearnCard is initialized, and you have contractUri and the userDidFromRedirect)
const contractUri = 'uri:contract:YOUR_CONTRACT_URI_HERE'; // From Part 1
const userDidFromRedirect = 'did:example:CONSENTING_USER_DID'; // From Part 3

async function readConsentedData(contractUriForRead: string, targetUserDid: string) {
    try {
        // Fetch all consented data for this contract
        // You might want to implement pagination for many users
        const consentData = await networkLearnCard.invoke.getConsentFlowData(contractUriForRead, {
            limit: 100,
        });
        console.log('Raw consented data for contract:', consentData.records);

        // Find the specific user's consented data using their DID
        // Note: The structure of records depends on what you asked for in the contract
        // and what the user consented to share. The user's DID is usually part of the
        // identity information within the consented data's "personal" or "credentials" section.
        // For this example, we'll assume you need to iterate and match.
        // A more direct API 'getConsentFlowDataForDid' might exist or might need specific query parameters.
        // For now, we'll iterate based on the provided API context.

        let userSpecificData = null;
        for (const record of consentData.records) {
            // How you find the DID depends on your data structure.
            // Let's assume a 'personal.did' field or similar might exist if you requested it.
            // Or, you might be linking the transaction to the DID server-side upon consent.
            // For this tutorial, let's log and assume you can identify the user.
            // The `getConsentFlowDataForDid` method might be more direct if the `did` parameter refers to the consenter:
            // const userData = await networkLearnCard.invoke.getConsentFlowDataForDid(targetUserDid, { query: { contractUris: [contractUriForRead] } });

            // For now, using getConsentFlowData and manually finding:
            // This part is highly dependent on how the DID is exposed in the returned records.
            // For simplicity in this tutorial, we'll just log the first record if it exists.
            if (record.personal && record.personal.Name) {
                // Example check
                console.log(`Processing record from date: ${record.date} for a user.`);
                // In a real app, you'd have a way to associate this record with targetUserDid
                // For now, if you're testing with one user, this first record might be theirs.
                userSpecificData = record; // Placeholder for finding the specific user's data
                break;
            }
        }

        if (userSpecificData) {
            console.log(`Data for user ${targetUserDid} (or first user found):`, userSpecificData);

            // Example: Accessing a shared "Learning History" credential URI
            if (userSpecificData.credentials?.categories?.['Learning History']?.[0]) {
                const credentialUri =
                    userSpecificData.credentials.categories['Learning History'][0];
                console.log('Found Learning History credential URI:', credentialUri);
                // You can now read this credential
                // const credential = await networkLearnCard.read.get(credentialUri);
                // console.log("Shared Credential:", credential);
            }
        } else {
            console.log(
                `No specific consented data found for DID ${targetUserDid} in the first page of results, or structure mismatch.`
            );
        }
    } catch (error) {
        console.error('Error reading consented data:', error);
    }
}

// Example usage:
// readConsentedData(contractUri, userDidFromRedirect);
```

{% hint style="info" %}
*Note on reading specific user data:* The `getConsentFlowData` retrieves all consented data for the contract. To get data for a specific user, you'd typically filter the results by the user's DID, or your backend might record the link between the `termsUri` (returned when a user consents) and the user's DID for more direct lookups via `getConsentFlowTransactions` or similar. The most direct way to get data for a *specific DID* for *your contracts* is often `getConsentFlowDataForDid`, then filtering the results for the relevant `contractUri` if that method returns data across multiple contracts. For this tutorial, we're simplifying.
{% endhint %}

### **Step 4.2: Sending a Credential to the User Through the Contract**

Your contract might allow you to `write` credentials to users who have consented.

```typescript
// (Assuming networkLearnCard, contractUri, userDidFromRedirect are defined)
// You'll also need a boostUri that acts as a template/category for the credential you're writing.
// Creating boosts is covered in other docs/tutorials. For here, assume you have one.
const relevantBoostUri = 'uri:boost:YOUR_RELEVANT_BOOST_URI'; // Replace!

// Define the credential you want to issue to this user
const boostTemplate = networkLearnCard.invoke.newCredential({
    type: 'boost',
});

const credentialTemplate = {
    ...boostTemplate,
    issuer: networkLearnCard.id.did(),
    name: 'Completed ConsentFlow CodePen Tutorial Step',
    credentialSubject: {
        ...boostTemplate.credentialSubject,
        id: consentedUserDidGlobal,
        achievement: {
            ...boostTemplate.credentialSubject.achievement,
            name: 'Completed ConsentFlow Tutorial Step',
            description: 'LearnCard Docs tutorial on ConsentFlow.',
            achievementType: 'LearnCard Docs',
        },
    },
};

const boostMetadata = {
    name: 'Completed ConsentFlow Tutorial Step',
    description: 'LearnCard Docs tutorial on ConsentFlow.',
    category: 'Achievement',
};

const boostUri = await networkLearnCard.invoke.createBoost(credentialTemplate, boostMetadata);

const newCredentialToIssue = await networkLearnCard.invoke.issueCredential({
    ...credentialTemplate,
    boostId: boostUri,
});

async function sendCredentialViaContract(
    consenterDid: string,
    contract: string,
    credential: any,
    boost: string
) {
    try {
        const issuedCredentialUri = await networkLearnCard.invoke.writeCredentialToContract(
            consenterDid,
            contract,
            credential,
            boost
        );
        console.log(
            'Credential successfully sent via contract! Issued Credential URI:',
            issuedCredentialUri
        );
        // The user might receive a notification if they have a webhook configured.
        return issuedCredentialUri;
    } catch (error) {
        console.error('Error sending credential via contract:', error);
        throw error;
    }
}

// Example usage:
// sendCredentialViaContract(userDidFromRedirect, contractUri, newCredentialToIssue, relevantBoostUri);
```

{% hint style="warning" %}
**Important:**

* The `credential.type` and its category must match what your ConsentFlow contract allows for `write` permissions.
* The `boostUri` parameter in `writeCredentialToContract` links the issued credential to a "Boost," which can act as a template or define its category and display properties. Ensure this Boost exists and your service profile has permission to use it.
  {% endhint %}

***

## Summary & Next Steps

You've now learned the end-to-end process of:

1. **Creating** a ConsentFlow contract.
2. **Generating a URL** for users to provide consent.
3. **Handling the redirect** to capture the consenting user's DID.
4. **Reading** data the user consented to share.
5. **Sending/Writing** a new credential to the user through the contract.

This is a foundational flow for many powerful applications. From here, you can explore more advanced ConsentFlow features like:

* [Updating and withdrawing consent.](/sdks/learncard-core/construction#retrieving-profiles-5)
* Using [Auto-Boosts](/core-concepts/consent-and-permissions/auto-boosts) to automatically issue credentials upon consent.
* Using an existing contract as a template in **Admin Tools → Manage ConsentFlow Contracts** by selecting **"Use as template"** from the contract detail view.
* More complex data queries.

Check out our Core Concept pages on [Consent Contracts](/core-concepts/consent-and-permissions/consent-contracts), [User Consent & Terms](/core-concepts/consent-and-permissions/user-consent-and-terms), and [Accessing Consented Data](/core-concepts/consent-and-permissions/accessing-consented-data) for more details.


# Create a Connected Website

Tutorial: Build 'Pixel Pet Designer' & Connect with LearnCard GameFlow

Welcome, game developer! This tutorial is a practical, step-by-step lesson where you'll build a simple web application called "Pixel Pet Designer." More importantly, you'll learn how to integrate it with LearnCard using **GameFlow** to issue unique, verifiable digital badges to your users for their creations, complete with guardian consent for younger players.

{% hint style="info" %}
**What is GameFlow?** GameFlow is LearnCard's specialized framework for educational games. It allows your game to securely interact with a player's LearnCard digital wallet to issue achievement badges and, optionally, track learning progress. A key feature is its built-in **guardian consent system**, ensuring a safe experience for younger learners. (For a deeper dive, see our [GameFlow Core Concept](/core-concepts/consent-and-permissions/gameflow-overview).)
{% endhint %}

## **Our Project: Pixel Pet Designer**

We'll create a fun, simple app where users can:

1. Design a pixel art pet on a 10x10 grid.
2. Name their pet.
3. "Submit" their design. Upon submission, our app will:
4. Prompt the user (and their guardian, if applicable) to connect their LearnCard account via GameFlow.
5. Issue a verifiable digital badge (a "Pixel Pet Creation" badge) containing the pet's name and its design data.

This tutorial focuses on the **learning experience** of integrating LearnCard. We'll keep the game logic itself very simple to concentrate on the GameFlow integration.

## **What you'll learn by doing:**

* How to set up your web application's backend as a LearnCard Issuer.
* How to define and create a **GameFlow Contract**, including `needsGuardianConsent`.
* How to implement a "Connect with LearnCard" button on your website.
* How your backend can handle the LearnCard callback, capturing the player's DID after consent (including the guardian flow).
* How to issue a Verifiable Credential (badge) with custom data (the pet's name and design) from your application.

## **Prerequisites:**

* **Node.js (v16+ recommended):** For running a simple backend server.
* **Basic HTML, CSS, and JavaScript knowledge:** We'll build a simple frontend.
* **Text Editor:** Your favorite code editor (e.g., VS Code).
* **LearnCard SDK Packages:** You'll install these in your project.
* **A Secure Seed for your Issuer:** A 64-character hexadecimal string. **For this tutorial, generate one for testing; for production, it must be cryptographically random and kept highly secure.**
* **Familiarity (Recommended):** Briefly review DIDs, Verifiable Credentials (VCs), and the general ConsentFlow Tutorial.

Let's get started!

## Part 1: Setting Up the "Pixel Pet Designer" Frontend

First, let's create the basic visual part of our application.

### **Step 1.1: Project Setup**

Create a new folder for your project, e.g., `pixel-pet-designer`. Inside it, create the following files:

* `index.html`
* `style.css`
* `app.js`
* `server.js` (We'll work on this later for the backend)

### **Step 1.2: `index.html` - The Basic Structure**

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Pixel Pet Designer</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <h1>Pixel Pet Designer</h1>
        
        <div class="controls">
            <label for="petName">Pet Name:</label>
            <input type="text" id="petName" placeholder="My Awesome Pet">
            
            <label for="colorPicker">Pick a Color:</label>
            <input type="color" id="colorPicker" value="#3498db">
        </div>

        <div id="pixelGrid" class="pixel-grid">
            </div>

        <button id="submitPetBtn">Create & Get Badge!</button>
        
        <div id="learnCardConnectArea" style="margin-top: 20px; padding: 15px; border: 1px dashed #ccc; display: none;">
            <p id="lcStatus">Connect your LearnCard to save your pet as a badge!</p>
            <button id="connectLearnCardBtn" style="display: none;">Connect with LearnCard</button>
        </div>

        <div id="badgeResultArea" style="margin-top: 20px; display: none;">
            <h3>Badge Issued!</h3>
            <p>Your "Pixel Pet Creation" badge has been sent to your LearnCard wallet.</p>
            <p>Pet Name: <span id="resultPetName"></span></p>
            <p>Pet Design (Data URI): <a id="resultPetDesignLink" href="#" target="_blank">View Design</a></p>
            <div id="resultPetPreview" style="width: 100px; height: 100px; border: 1px solid black; margin-top: 10px;"></div>
        </div>
    </div>
    <script src="app.js"></script>
</body>
</html>

```

### **Step 1.3: `style.css` - Basic Styling**

```css
body {
    font-family: sans-serif;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    background-color: #f0f0f0;
    margin: 0;
    color: #333;
}
.container {
    background-color: white;
    padding: 25px;
    border-radius: 8px;
    box-shadow: 0 0 15px rgba(0,0,0,0.1);
    text-align: center;
}
.controls {
    margin-bottom: 20px;
    display: flex;
    gap: 15px;
    align-items: center;
    justify-content: center;
}
.controls label {
    font-weight: bold;
}
.controls input[type="text"] {
    padding: 8px;
    border: 1px solid #ccc;
    border-radius: 4px;
}
.controls input[type="color"] {
    width: 40px;
    height: 40px;
    border: none;
    padding: 0;
    border-radius: 4px;
    cursor: pointer;
}
.pixel-grid {
    display: grid;
    grid-template-columns: repeat(10, 30px);
    grid-template-rows: repeat(10, 30px);
    width: 300px; /* 10 * 30px */
    height: 300px; /* 10 * 30px */
    border: 1px solid #ccc;
    margin: 20px auto;
}
.pixel {
    width: 30px;
    height: 30px;
    background-color: #ffffff;
    border: 1px solid #eee;
    box-sizing: border-box;
}
.pixel:hover {
    opacity: 0.7;
}
button {
    background-color: #007bff;
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font-size: 16px;
    transition: background-color 0.2s;
}
button:hover {
    background-color: #0056b3;
}
button:disabled {
    background-color: #ccc;
    cursor: not-allowed;
}

```

### **Step 1.4: `app.js` - Basic Frontend Logic (Pixel Grid)**

```typescript
// app.js
const pixelGrid = document.getElementById('pixelGrid');
const colorPicker = document.getElementById('colorPicker');
const petNameInput = document.getElementById('petName');
const submitPetBtn = document.getElementById('submitPetBtn');

const learnCardConnectArea = document.getElementById('learnCardConnectArea');
const connectLearnCardBtn = document.getElementById('connectLearnCardBtn');
const lcStatus = document.getElementById('lcStatus');

const badgeResultArea = document.getElementById('badgeResultArea');
const resultPetName = document.getElementById('resultPetName');
const resultPetDesignLink = document.getElementById('resultPetDesignLink');
const resultPetPreview = document.getElementById('resultPetPreview');


const gridSize = 10;
let currentDrawingColor = colorPicker.value;
let pixelData = Array(gridSize * gridSize).fill('#ffffff'); // Store colors

colorPicker.addEventListener('input', (event) => {
    currentDrawingColor = event.target.value;
});

// Create Grid
for (let i = 0; i < gridSize * gridSize; i++) {
    const pixel = document.createElement('div');
    pixel.classList.add('pixel');
    pixel.addEventListener('click', () => {
        pixel.style.backgroundColor = currentDrawingColor;
        pixelData[i] = currentDrawingColor;
    });
    pixelGrid.appendChild(pixel);
}

// Initially, the submit button might be disabled until LearnCard is connected
submitPetBtn.disabled = true; 
learnCardConnectArea.style.display = 'block'; // Show connection area
connectLearnCardBtn.style.display = 'inline-block'; // Show connect button

// We'll add more logic for LearnCard connection and pet submission later.
logToPage("Frontend initialized. Ready to connect LearnCard.");

function logToPage(message) { // Simple logger for frontend messages
    console.log(message);
    // In a real app, you might have a dedicated status area on the page.
}

```

At this point, you should be able to open `index.html` in your browser and see the Pixel Pet Designer interface. You can draw, but submitting doesn't do anything related to LearnCard yet.

## Part 2: Setting Up the Backend (Issuer & GameFlow Contract)

Your game's backend will handle LearnCard initialization, contract creation, and credential issuance. We'll use Node.js with conceptual Express-like routing for this tutorial.

### **Step 2.1: Install Dependencies**

In your project folder (`pixel-pet-designer`), open your terminal:

```bash
npm init -y
npm install express @learncard/init # Add any other backend dependencies
# If using TypeScript for backend: npm install typescript @types/express @types/node ts-node --save-dev

```

### **Step 2.2: Backend Setup (`server.js`)**

Create `server.js`:

```typescript
// server.js
const express = require('express'); // Or import express from 'express'; if using ES modules
const { initLearnCard } = require('@learncard/init'); 

const app = express();
const port = 3000; // Port for your backend server

app.use(express.json()); // Middleware to parse JSON bodies

// --- LearnCard Issuer Setup ---
// IMPORTANT: Store your seed securely (e.g., environment variable in production)
const PIXELPET_ISSUER_SEED = 'YOUR_PIXELPET_GAME_SECURE_64_CHAR_HEX_SEED'; // Replace!
const PIXELPET_PROFILE_ID = 'pixelpet-designer-game';
const PIXELPET_DISPLAY_NAME = 'Pixel Pet Designer Official';

// This URL must be publicly accessible for LearnCard redirect. 
const YOUR_GAME_CALLBACK_URL = 'http://localhost:3000/learncard-callback'; 

let gameLearnCardInstance;
let gameFlowContractUriCache = null;

async function initializeLearnCardIssuer() {
    if (gameLearnCardInstance) return gameLearnCardInstance;
    if (PIXELPET_ISSUER_SEED === 'YOUR_PIXELPET_GAME_SECURE_64_CHAR_HEX_SEED') {
        console.error("FATAL: Please replace YOUR_PIXELPET_GAME_SECURE_64_CHAR_HEX_SEED in server.js");
        process.exit(1);
    }

    console.log('Initializing Pixel Pet Issuer LearnCard SDK...');
    gameLearnCardInstance = await initLearnCard({
        seed: PIXELPET_ISSUER_SEED,
        network: true,
        allowRemoteContexts: true,
    });
    console.log("Pixel Pet Issuer LearnCard Initialized.");
    console.log("Pixel Pet Issuer DID:", gameLearnCardInstance.id.did());

    // Ensure Service Profile
    try {
        let profile = await gameLearnCardInstance.invoke.getProfile(PIXELPET_PROFILE_ID);
        if (!profile) {
            console.log(`Creating service profile: ${PIXELPET_PROFILE_ID}`);
            await gameLearnCardInstance.invoke.createServiceProfile({
                profileId: PIXELPET_PROFILE_ID,
                displayName: PIXELPET_DISPLAY_NAME,
            });
            console.log('Pixel Pet Service Profile created.');
        } else {
            console.log('Pixel Pet Service Profile already exists.');
        }
    } catch (error) {
        if (error.message?.includes('Profile ID already exists')) {
            console.log('Pixel Pet Service Profile already exists (confirmed by error).');
        } else {
            console.error(`Failed to ensure Pixel Pet profile: ${error.message}`);
        }
    }
    return gameLearnCardInstance;
}

// --- GameFlow Contract Definition & Creation ---
const pixelPetGameFlowContract = {
    name: "Pixel Pet Designer - Badge Connection",
    subtitle: "Connect to save your pixel pet designs as verifiable badges!",
    description: "Allows Pixel Pet Designer to issue you a unique badge for each pet you create. Guardian consent is required for younger designers.",
    needsGuardianConsent: true, // KEY for GameFlow
    redirectUrl: YOUR_GAME_CALLBACK_URL,
    reasonForAccessing: "Pixel Pet Designer needs permission to issue you a digital badge for your created pet. This badge will include your pet's name and its design.",
    contract: {
        read: { /* We don't need to read anything for this simple example */ },
        write: {
            credentials: {
                categories: { "Achievement": { required: true } } // We will issue "Achievement" badges
            }
        }
    }
};

async function getOrCreateGameFlowContract() {
    if (gameFlowContractUriCache) return gameFlowContractUriCache;
    if (!gameLearnCardInstance) await initializeLearnCardIssuer();

    console.log('Creating Pixel Pet GameFlow Contract...');
    try {
        const uri = await gameLearnCardInstance.invoke.createContract(pixelPetGameFlowContract);
        console.log('Pixel Pet GameFlow Contract Created! URI:', uri);
        gameFlowContractUriCache = uri;
        return uri;
    } catch (error) {
        console.error('Error creating GameFlow contract:', error);
        throw error;
    }
}

// Initialize everything on server start
(async () => {
    await initializeLearnCardIssuer();
    await getOrCreateGameFlowContract(); // Create contract on startup
})();

// --- API Endpoints for Frontend ---
app.get('/api/get-consent-url', async (req, res) => {
    try {
        const contractUri = await getOrCreateGameFlowContract();
        // The frontend will redirect the user to this LearnCard page
        const consentUrl = `https://learncard.app/consent-flow?uri=${encodeURIComponent(contractUri)}&returnTo=${encodeURIComponent(YOUR_GAME_CALLBACK_URL)}`;
        res.json({ consentUrl });
    } catch (error) {
        console.error("Error in /api/get-consent-url:", error);
        res.status(500).json({ error: "Could not generate consent URL." });
    }
});

// We'll add more endpoints later for the callback and issuing badges.

app.listen(port, () => {
    console.log(`Pixel Pet Designer backend listening on http://localhost:${port}`);
    console.log(`Ensure your GameFlow Contract redirectUrl is set to: ${YOUR_GAME_CALLBACK_URL}`);
});

```

{% hint style="success" %}

## **Action**

* Replace `YOUR_PIXELPET_GAME_SECURE_64_CHAR_HEX_SEED` with a unique 64-character hex string.
* Start your backend: `node server.js` (or `npx ts-node server.js` if using TypeScript).
  {% endhint %}

## Part 3: Connecting LearnCard - Frontend

Now, let's make the "Connect with LearnCard" button functional.

### **Step 3.1: Update `app.js` to Fetch Consent URL**

```typescript
// app.js - (add to existing app.js content)

// ... (keep existing variable declarations and grid creation logic) ...

connectLearnCardBtn.addEventListener('click', async () => {
    logToPage("Attempting to connect LearnCard...");
    connectLearnCardBtn.disabled = true;
    lcStatus.textContent = "Preparing connection...";

    try {
        // Ask our backend for the consent URL (which includes our GameFlow Contract URI)
        const response = await fetch('/api/get-consent-url'); // Assumes frontend and backend on same origin during dev, or configure CORS
        if (!response.ok) {
            throw new Error(`Failed to get consent URL: ${response.statusText}`);
        }
        const data = await response.json();
        
        if (data.consentUrl) {
            logToPage(`Redirecting to LearnCard for consent: ${data.consentUrl}`);
            window.location.href = data.consentUrl; // Redirect user
        } else {
            throw new Error("Consent URL not received from backend.");
        }
    } catch (error) {
        logToPage(`Error connecting LearnCard: ${error.message}`);
        lcStatus.textContent = "Connection failed. Please try again.";
        connectLearnCardBtn.disabled = false;
    }
});

// ... (rest of app.js) ...

```

{% hint style="success" %}

## **Action**

Test this. Open `index.html`. Your backend server (`server.js`) should be running. Clicking the "Connect with LearnCard" button should now redirect you to `learncard.app`. You'll see your GameFlow contract details. Since `needsGuardianConsent: true`, you'll be guided through the guardian consent flow.
{% endhint %}

## Part 4: Connecting LearnCard - Backend Callback

After the user (and guardian, if applicable) consents on `learncard.app`, they are redirected back to `YOUR_GAME_CALLBACK_URL` (`http://localhost:3000/learncard-callback`).

### **Step 4.1: Implement the Callback Endpoint in `server.js`**

```typescript
// server.js - (add this endpoint to your Express app)

// ... (keep existing setup and /api/get-consent-url endpoint) ...

app.get('/learncard-callback', async (req, res) => {
    const userDid = req.query.did; // The DID of the child/player, if guardian flow completed successfully
    const delegateVpJwt = req.query.vp; // Optional: Verifiable Presentation with Delegate Credential for xAPI

    console.log('Received LearnCard Callback:');
    console.log('  User/Player DID:', userDid);
    console.log('  Delegate VP JWT (first 50 chars if any):', delegateVpJwt ? delegateVpJwt.substring(0,50) + '...' : 'N/A');

    if (!userDid) {
        console.error('LearnCard callback error: User DID not found.');
        // Redirect to an error page on your frontend
        return res.redirect('/#learncard-error=did_missing'); 
    }

    // --- IMPORTANT: Link userDid to your game's internal player account ---
    // In a real game, you'd have a way to know which of YOUR game players this callback is for.
    // This might involve:
    // 1. Storing a temporary state in the user's session before redirecting to LearnCard.
    // 2. Using the `returnTo` URL to pass back a unique identifier for your game session.
    // For this tutorial, we'll assume you can identify the game player.
    const internalGamePlayerId = "player123"; // Placeholder - replace with actual player ID logic

    console.log(`Associating LearnCard DID ${userDid} with game player ${internalGamePlayerId}.`);
    // Example: Save to your database
    // await YourGameDatabase.linkLearnCardDid(internalGamePlayerId, userDid, guardianDid, delegateVpJwt);
    
    // For the frontend to know, we can use a query parameter or session state
    // Redirecting to a page that indicates success and stores the DID client-side (for demo purposes)
    // In production, you'd likely handle this association server-side and update session state.
    const frontendRedirectUrl = `/index.html?learncard_did=${encodeURIComponent(userDid)}&status=connected`;
    res.redirect(frontendRedirectUrl);
});

// ... (rest of server.js) ...

```

### **Step 4.2: Frontend - Handle Successful Connection (`app.js`)**

Update `app.js` to check for the `learncard_did` from the redirect and update the UI.

```typescript
// app.js - (add this to existing app.js content)

// ... (existing variables) ...

window.addEventListener('DOMContentLoaded', () => {
    const queryParams = new URLSearchParams(window.location.search);
    const connectedDid = queryParams.get('learncard_did');
    const connectionStatus = queryParams.get('status');

    if (connectionStatus === 'connected' && connectedDid) {
        logToPage(`Successfully connected LearnCard! Player DID: ${connectedDid}`);
        lcStatus.textContent = `LearnCard Connected! DID: ${connectedDid.substring(0, 20)}...`;
        connectLearnCardBtn.style.display = 'none';
        submitPetBtn.disabled = false; // Enable pet submission!

        // Store the DID for later use (e.g., when submitting the pet)
        sessionStorage.setItem('learnCardPlayerDid', connectedDid);

        // Clean up URL params (optional)
        // window.history.replaceState({}, document.title, "/index.html"); 
    } else if (window.location.hash.includes('learncard-error')) {
        logToPage("LearnCard connection failed or was cancelled.");
        lcStatus.textContent = "LearnCard connection failed. Please try again.";
        connectLearnCardBtn.disabled = false;
    }
});

// ... (rest of app.js) ...

```

{% hint style="success" %}

## **Action**

* Restart your `server.js`.
* Go through the "Connect LearnCard" flow from your `index.html`.
* Complete the consent (and guardian steps if prompted) on `learncard.app`.
* You should be redirected back to `index.html` (or your specified callback, then to index.html), and the UI should update to show the connected DID. The "Create & Get Badge!" button should now be enabled.
  {% endhint %}

## Part 5: Designing & Submitting a Pet (Frontend)

This part focuses on capturing the pet design and name.

### **Step 5.1: Update `app.js` for Pet Submission**

```typescript
// app.js - (add to existing app.js content)

// ... (existing variables and functions) ...

submitPetBtn.addEventListener('click', async () => {
    const petName = petNameInput.value.trim();
    const connectedDid = sessionStorage.getItem('learnCardPlayerDid');

    if (!petName) {
        alert("Please give your pixel pet a name!");
        return;
    }
    if (!connectedDid) {
        alert("LearnCard not connected. Please connect your LearnCard first.");
        // Optionally, re-trigger connection flow or show the button
        learnCardConnectArea.style.display = 'block';
        connectLearnCardBtn.style.display = 'inline-block';
        submitPetBtn.disabled = true;
        return;
    }

    logToPage(`Submitting pet: ${petName} for DID: ${connectedDid}`);
    submitPetBtn.disabled = true;
    lcStatus.textContent = "Creating your pet badge...";

    // Prepare pet data (name and pixelData)
    const petSubmissionData = {
        playerName: petName, // Using the input field as player/pet name for simplicity
        petDesign: pixelData, // The array of colors from the grid
        playerLearnCardDid: connectedDid
    };

    try {
        // Send this data to your backend to issue the badge
        const response = await fetch('/api/issue-pet-badge', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(petSubmissionData)
        });

        if (!response.ok) {
            const errorResult = await response.json();
            throw new Error(errorResult.error || `Failed to issue badge: ${response.statusText}`);
        }

        const result = await response.json();
        logToPage(`Badge issued successfully! Credential URI: ${result.credentialUri}`);
        lcStatus.textContent = "Badge Issued! Check your LearnCard App.";

        // Display badge result on page
        resultPetName.textContent = result.issuedPetName;
        
        // Create a data URI for the pixel art to display it
        const canvas = document.createElement('canvas');
        canvas.width = 10 * 10; // 10 pixels per grid square, 10 squares
        canvas.height = 10 * 10;
        const ctx = canvas.getContext('2d');
        for (let r = 0; r < gridSize; r++) {
            for (let c = 0; c < gridSize; c++) {
                ctx.fillStyle = pixelData[r * gridSize + c];
                ctx.fillRect(c * 10, r * 10, 10, 10);
            }
        }
        const petImageURI = canvas.toDataURL();
        resultPetDesignLink.href = petImageURI;
        resultPetPreview.innerHTML = `<img src="${petImageURI}" alt="Pixel Pet Preview" style="image-rendering: pixelated; width: 100%; height: 100%;">`;
        
        badgeResultArea.style.display = 'block';

    } catch (error) {
        logToPage(`Error issuing pet badge: ${error.message}`);
        lcStatus.textContent = "Failed to issue badge. Please try again.";
    } finally {
        submitPetBtn.disabled = false; // Re-enable for another try or new pet
    }
});

// ... (rest of app.js) ...

```

## Part 6: Issuing the Pet Badge (Backend)

When the frontend submits the pet data, your backend issues the Verifiable Credential.

### **Step 6.1: Create a Boost for "Pixel Pet Creation" Badges (One-time Setup)**

Add this to your `server.js` (or a separate `badge-manager.js` like in the previous tutorial).

```typescript
// server.js - (add near other LearnCard setup)
// ... (imports and existing setup) ...

const PIXELPET_BADGE_BOOST_NAME = 'Pixel Pet Creation Badge';
let pixelPetBoostUriCache = null;

async function getOrCreatePixelPetBoost() {
    if (pixelPetBoostUriCache) return pixelPetBoostUriCache;
    if (!gameLearnCardInstance) await initializeLearnCardIssuer();

    const badgeTemplate = {
        "@context": [
            "https://www.w3.org/2018/credentials/v1",
            "https://purl.imsglobal.org/spec/ob/v3p0/context.json",
            "https://ctx.learncard.com/boosts/1.0.3.json"
        ],
        type: ["VerifiableCredential", "OpenBadgeCredential", "BoostCredential"],
        name: PIXELPET_BADGE_BOOST_NAME, // This will be part of the issued VC name
        credentialSubject: {
            // id will be the recipient's DID
            achievement: {
                achievementType: "Badge",
                name: "Pixel Pet Creator", // General name for the achievement type
                description: "Awarded for designing and creating a unique Pixel Pet!",
                id: "urn:uuid:" + require('crypto').randomUUID(), // Unique ID for this achievement definition
                image: "https://placehold.co/100x100/FFD700/000000?text=Pet!", // Placeholder badge image for the Boost
                type: ["Achievement"],
                criteria: { narrative: "User designed a custom pixel art pet and named it."}
            },
            type: ["AchievementSubject"]
        },
        display: { backgroundColor: "#FF69B4", displayType: "badge" }, // Hot Pink!
        image: "https://placehold.co/100x100/FF69B4/FFFFFF?text=Pet!"
    };
    const boostMetadata = { 
        name: PIXELPET_BADGE_BOOST_NAME, 
        description: 'Awards a badge for creating a Pixel Pet.', 
        category: 'Achievement' 
    };
    try {
        console.log('Creating Pixel Pet Creation Boost...');
        const uri = await gameLearnCardInstance.invoke.createBoost(badgeTemplate, boostMetadata);
        console.log('Pixel Pet Creation Boost Created! URI:', uri);
        pixelPetBoostUriCache = uri;
        return uri;
    } catch (error) {
        console.error('Error creating Pixel Pet Boost:', error);
        throw error;
    }
}

// Ensure this is called on server startup, after initializeLearnCardIssuer()
// Add to your IIFE:
// (async () => {
//     await initializeLearnCardIssuer();
//     await getOrCreateGameFlowContract(); 
//     await getOrCreatePixelPetBoost(); // Add this line
// })();

```

{% hint style="success" %}

## **Action**

Update your server startup to include `await getOrCreatePixelPetBoost();`.
{% endhint %}

### **Step 6.2: Backend - Create the `/api/issue-pet-badge` Endpoint**

Add this new route to your `server.js`.

```typescript
// server.js - (add this endpoint)
// ... (imports and existing setup and routes) ...

app.post('/api/issue-pet-badge', async (req, res) => {
    if (!gameLearnCardInstance || !gameFlowContractUriCache || !pixelPetBoostUriCache) {
        return res.status(500).json({ error: "Backend not fully initialized." });
    }

    const { playerName, petDesign, playerLearnCardDid } = req.body;

    if (!playerName || !petDesign || !playerLearnCardDid) {
        return res.status(400).json({ error: "Missing pet name, design, or player DID." });
    }

    console.log(`Attempting to issue Pixel Pet badge for ${playerName} to DID ${playerLearnCardDid}`);

    try {
        // The credential we issue will be an *instance* of the Pixel Pet Boost.
        // We personalize the `credentialSubject` for this specific instance.
        const petCredentialInstance = {
            "@context": [
                "https://www.w3.org/2018/credentials/v1",
                "https://purl.imsglobal.org/spec/ob/v3p0/context.json",
                "https://ctx.learncard.com/boosts/1.0.3.json"
            ],
            id: "urn:uuid:" + require('crypto').randomUUID(), // Unique ID for THIS credential instance
            type: ["VerifiableCredential", "OpenBadgeCredential", "BoostCredential"],
            name: 'Pixel Pet Creation Badge', // This will be part of the issued VC name
            issuer: gameLearnCardInstance.id.did(), // Your game's DID
            issuanceDate: new Date().toISOString(),
            credentialSubject: {
                id: playerLearnCardDid, // The player's DID
                achievement: {
                    achievementType: "Badge",
                    name: "Pixel Pet Creator", // General name for the achievement type
                    description: "Awarded for designing and creating a unique Pixel Pet!",
                    id: "urn:uuid:" + require('crypto').randomUUID(), // Unique ID for this achievement definition
                    image: "https://placehold.co/100x100/FFD700/000000?text=Pet!", // Placeholder badge image for the Boost
                    type: ["Achievement"],
                    criteria: { narrative: "User designed a custom pixel art pet and named it."}
                },
                type: ["AchievementSubject"]
            },
            display: { backgroundColor: "#FF69B4", displayType: "badge" }, // Hot Pink!
            image: "https://placehold.co/100x100/FF69B4/FFFFFF?text=Pet!"
        }
        
        const issuedPetCredentialInstance = await gameLearnCardInstance.issueCredential(petCredentialInstance) 

        console.log(`Issuing Pixel Pet badge to ${playerLearnCardDid} via contract ${gameFlowContractUriCache}`);
        const issuedCredentialUri = await gameLearnCardInstance.invoke.writeCredentialToContract(
            playerLearnCardDid,
            gameFlowContractUriCache,
            issuedPetCredentialInstance,
            pixelPetBoostUriCache // URI of the "Pixel Pet Creation" Boost
        );

        console.log(`Pixel Pet Badge successfully issued to ${playerLearnCardDid}! Credential URI: ${issuedCredentialUri}`);
        res.json({ 
            success: true, 
            credentialUri: issuedCredentialUri,
            issuedPetName: playerName 
        });

    } catch (error) {
        console.error(`Error issuing Pixel Pet badge to DID ${playerLearnCardDid}:`, error);
        res.status(500).json({ error: "Failed to issue badge. " + error.message });
    }
});

// ... (rest of server.js, including app.listen) ...

```

{% hint style="success" %}

## **Action**

1. Add the `getOrCreatePixelPetBoost` function and call it during server startup.
2. Add the `/api/issue-pet-badge` endpoint to your `server.js`.
3. Restart your `server.js`.
4. Go back to your `index.html` in the browser.
   * Connect LearnCard (if not already).
   * Design your pixel pet, give it a name.
   * Click "Create & Get Badge!"

You should see logs on both your frontend and backend. The frontend will update to show the badge was issued, and if you check the LearnCard app associated with the `playerLearnCardDid`, you should find your new "Pixel Pet: \[YourPetName]" badge!
{% endhint %}

## Summary & Next Steps

Congratulations! You've successfully built "Pixel Pet Designer" and integrated it with LearnCard GameFlow to: ✅ Set up your game as a LearnCard Issuer. ✅ Create a GameFlow Contract enabling guardian consent. ✅ Allow players/guardians to connect their LearnCard accounts. ✅ Handle the consent callback and link player DIDs. ✅ Automatically issue a custom "Pixel Pet Creation" badge when a pet is designed.

This tutorial demonstrates a powerful way to add verifiable achievements and data portability to your educational games.

From here, you can explore:

* **Storing Pet Designs:** Instead of just embedding pixel data in the VC, you could save the design (e.g., as an image or JSON on a server) and include a URL to it in the VC's `evidence` field.
* **xAPI Integration:** Use the `delegateVpJwt` (captured in Part 4) to [send xAPI statements about game activities ](/tutorials/sending-xapi-statements)(e.g., "Player X started designing a pet," "Player X submitted pet Y").
* **Advanced Boost Features:** Explore Boost permissions, hierarchies, and more detailed display customizations.
* **Error Handling & UI/UX:** Improve the user interface, error messages, and overall flow for a production-ready game.

You're now equipped to bring the power of Verifiable Credentials and LearnCard GameFlow to your own educational projects!


# Send xAPI Statements

This tutorial will walk you through the essential steps to send an xAPI statement to LearnCloud Storage and then read it back. We'll keep it simple so you can get up and running quickly!

## **What you'll accomplish:**

* Construct a basic xAPI statement.
* Send the statement to the LearnCloud xAPI endpoint.
* Retrieve and verify the statement you sent.
* View xAPI statements in the LearnCard app's Activity Feed.

{% embed url="<https://codepen.io/Jacks-n-Smith/pen/xbbBmBV>" fullWidth="false" %}

## **Prerequisites:**

1. **Understanding Key Concepts:**
   * **What is xAPI?** xAPI (Experience API) is a way to track learning experiences using a simple "Actor - Verb - Object" structure (e.g., "Sarah completed 'Safety Course'"). For a deeper dive, see our [Understanding xAPI Data in LearnCard](/core-concepts/credentials-and-data/xapi-data) core concept page.
   * **What is a DID?** A DID (Decentralized Identifier) is a unique identifier for your user. Think of it as a secure, private digital ID. More details can be found on our [Understanding DIDs](/core-concepts/identities-and-keys/decentralized-identifiers-dids) core concept page.
2. **Your Environment:**
   * You have the [LearnCard SDK ](/sdks/learncard-core)initialized in your project.
   * You have obtained a **JSON Web Token (JWT)** for authentication. This JWT represents the authenticated user (the "actor"). As an example of how to create this JWT, check out the[ "Create a Connected Website Tutorial."](/tutorials/create-a-connected-website)
   * You have the **DID** of the authenticated user.
   * The default LearnCloud xAPI endpoint is `https://cloud.learncard.com/xapi/statements`.

***

## Part 1: Sending an xAPI Statement

Let's send a statement indicating a user has attempted a challenge in a game.

{% stepper %}
{% step %}

### **Define Your xAPI Statement**

An xAPI statement has three main parts: an `actor` (who did it), a `verb` (what they did), and an `object` (what they did it to).

```typescript
// Placeholders: Replace with your actual data
const userDid = 'did:example:YOUR_USER_DID'; // The DID of the user performing the action
const jwtToken = 'YOUR_JWT_TOKEN'; // Your authentication JWT
const xapiEndpoint = 'https://cloud.learncard.com/xapi/statements';

const attemptStatement = {
    actor: {
        objectType: 'Agent',
        name: userDid, // Use the user's DID here
        account: {
            homePage: 'https://www.w3.org/TR/did-core/', // Standard homepage for DID accounts
            name: userDid, // Crucial: Also use the user's DID here
        },
    },
    verb: {
        id: 'http://adlnet.gov/expapi/verbs/attempted', // A standard xAPI verb URI
        display: {
            'en-US': 'attempted', // Human-readable display for the verb
        },
    },
    object: {
        id: 'http://yourgame.com/activities/level-1-challenge', // A unique URI for your activity
        definition: {
            name: { 'en-US': 'Level 1 Challenge' },
            description: { 'en-US': 'The first exciting challenge of the game.' },
            type: 'http://adlnet.gov/expapi/activities/simulation', // Type of activity
        },
    },
};

// Type interface for clarity (optional, but good practice)
interface XAPIStatement {
    actor: {
        objectType: 'Agent';
        name: string;
        account: { homePage: string; name: string };
    };
    verb: {
        id: string;
        display: { 'en-US': string };
    };
    object: {
        id: string;
        definition: {
            name: { 'en-US': string };
            description: { 'en-US': string };
            type: string;
        };
    };
    result?: any; // Optional result object
}
```

✨ **Good to know:**

* **DID Usage:** For LearnCloud, ensure the `userDid` is used in both `actor.name` and `actor.account.name`.
* **Verb Selection:** Use standard xAPI verb URIs when possible. You can find lists of common verbs online (e.g., on the ADLNet website).
* **Activity IDs:** Make your `object.id` URIs unique for each distinct activity. They don't need to be real, live URLs.
  {% endstep %}

{% step %}
**Prepare and Send the Statement**

We'll use a `Workspace` request to send this statement. The key things are the `POST` method, correct headers, and the statement in the body.

```typescript
async function sendStatement(statement: XAPIStatement, token: string, endpointUrl: string) {
    console.log('Sending xAPI Statement:', JSON.stringify(statement, null, 2));

    try {
        const response = await fetch(endpointUrl, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-Experience-API-Version': '1.0.3', // Standard xAPI version header
                'X-VP': token, // LearnCloud specific: Your JWT for authentication
            },
            body: JSON.stringify(statement),
        });

        if (!response.ok) {
            // If the server response is not OK (e.g., 400, 401, 500)
            let errorData;
            try {
                errorData = await response.json(); // Try to parse a JSON error response
            } catch (e) {
                errorData = { status: response.status, statusText: response.statusText }; // Fallback if no JSON body
            }
            console.error('xAPI Statement Error:', errorData);
            throw new Error(
                `Failed to send xAPI statement: ${response.status} ${response.statusText}`
            );
        }

        // If successful, the LRS usually returns an array with the ID of the stored statement
        const responseData = await response.json();
        console.log('xAPI Statement Sent Successfully! Response:', responseData);
        return responseData; // This often is an array with the statement ID(s)
    } catch (networkError) {
        console.error('Network or other error sending xAPI statement:', networkError);
        throw networkError;
    }
}

// Let's send our 'attemptStatement'
// (Ensure userDid and jwtToken are defined as in Step 1)
sendStatement(attemptStatement, jwtToken, xapiEndpoint)
    .then(ids => {
        if (ids && ids.length > 0) {
            console.log('Statement ID received:', ids[0]);
            // You might want to store this ID if you plan to void the statement later.
        }
    })
    .catch(error => {
        // Error already logged in sendStatement, but you can do more here if needed
    });
```

{% endstep %}

{% step %}
**Check the Response**

If successful, the LearnCloud Storage API will typically return an HTTP status like `200 OK` or `204 No Content`. Often, a `200 OK` response to a `POST` will include an array containing the unique ID(s) of the statement(s) just stored. Our `sendStatement` function logs this.
{% endstep %}
{% endstepper %}

***

## Part 2: Reading xAPI Statements

Now that we've sent a statement, let's try to read statements for that user.

{% stepper %}
{% step %}

### **Prepare Your Request Parameters**

To read statements, you'll usually query for statements related to a specific `agent` (the actor). The `agent` parameter must be a JSON string.

```typescript
// (Ensure userDid and jwtToken are defined as in Part 1, Step 1)
// And xapiEndpoint is also defined: const xapiEndpoint = 'https://cloud.learncard.com/xapi/statements';

// Define the actor (agent) for whom you want to retrieve statements
const actorToQuery = {
    objectType: 'Agent',
    name: userDid,
    account: {
        homePage: 'https://www.w3.org/TR/did-core/',
        name: userDid,
    },
};

// Construct URL parameters
const params = new URLSearchParams({
    agent: JSON.stringify(actorToQuery), // Key parameter: filter by agent
    // You can add other parameters like 'verb', 'activity', 'since', 'limit' here
    // For example: limit: '10'
});
```

{% endstep %}

{% step %}
**Make the API Call to Read Statements**

This will be a `GET` request.

```typescript
async function readStatements(queryParams: URLSearchParams, token: string, endpointUrl: string) {
    console.log(`Reading xAPI Statements with params: ${queryParams.toString()}`);

    try {
        const response = await fetch(`${endpointUrl}?${queryParams.toString()}`, {
            method: 'GET',
            headers: {
                // 'Content-Type': 'application/json', // Not strictly needed for GET, but often included
                'X-Experience-API-Version': '1.0.3',
                'X-VP': token, // Your authentication JWT
            },
        });

        if (!response.ok) {
            let errorData;
            try {
                errorData = await response.json();
            } catch (e) {
                errorData = { status: response.status, statusText: response.statusText };
            }
            console.error('Error Reading xAPI Statements:', errorData);
            throw new Error(
                `Failed to read xAPI statements: ${response.status} ${response.statusText}`
            );
        }

        const data = await response.json();
        console.log('Successfully Read xAPI Statements:', data);
        return data; // Contains 'statements' array and possibly a 'more' link for pagination
    } catch (networkError) {
        console.error('Network or other error reading xAPI statements:', networkError);
        throw networkError;
    }
}

// Let's read statements for our user
// (Ensure params, jwtToken, and xapiEndpoint are defined)
readStatements(params, jwtToken, xapiEndpoint)
    .then(data => {
        if (data.statements && data.statements.length > 0) {
            console.log(`Found ${data.statements.length} statement(s).`);
            // You can now iterate through data.statements
            // Try to find the statement you sent earlier!
            const myStatement = data.statements.find(
                stmt => stmt.object.id === 'http://yourgame.com/activities/level-1-challenge'
            );
            if (myStatement) {
                console.log('Found the statement we sent:', myStatement);
            }
        } else {
            console.log('No statements found for this agent or an error occurred.');
        }
        if (data.more) {
            console.log('More statements available at:', data.more);
        }
    })
    .catch(error => {
        // Error already logged
    });
```

{% endstep %}

{% step %}
**Process the Response**

The response from a `GET` request to the `/statements` endpoint will be a JSON object. This object typically contains an array called `statements` and optionally a `more` property. The `more` property provides a URL to fetch the next page of results if pagination is active.
{% endstep %}
{% endstepper %}

***

## Part 3: Viewing xAPI Statements in the LearnCard App

Once xAPI statements have been sent, users can view their activity data directly in the LearnCard app. This provides a user-friendly way to see all learning activities associated with a specific app or contract.

{% stepper %}
{% step %}

### **Navigate to Data Sharing**

From the LearnCard app:

1. Tap on your **profile picture** in the top corner
2. Select **"Manage Data Sharing"** to see all your consented apps and contracts
   {% endstep %}

{% step %}

### **Select a Contract**

In the Data Sharing view, you'll see a list of apps you've consented to share data with. Tap on any contract to view its details.
{% endstep %}

{% step %}

### **Open the Activity Feed**

In the contract detail view, look for the **"xAPI Data Feed"** button. Tap it to open the Activity Feed modal.
{% endstep %}

{% step %}

### **View Your Activity Data**

The Activity Feed displays all xAPI statements associated with that specific contract, including:

* **Verb badges** (e.g., "completed", "attempted", "mastered") with color-coded styling
* **Activity name and description**
* **Timestamps** showing when each activity occurred
* **Result data** (scores, completion status, success/failure)
* **Raw JSON view** for developers who want to inspect the full statement

The feed supports infinite scrolling, so you can browse through all your historical activity data.
{% endstep %}
{% endstepper %}

{% hint style="info" %}
**For Developers:** The Activity Feed filters statements by the `contractUri` extension in `context.extensions`. When you send xAPI statements with a contract URI (as shown in [Contract-Scoped xAPI Statements](/sdks/learncloud-storage-api/xapi-reference#contract-scoped-xapi-statements)), they will automatically appear in this feed for the associated contract.
{% endhint %}

***

## Important Considerations (Recap)

* **Authentication (`X-VP` Header):** All requests to the LearnCloud xAPI endpoint must include a valid JWT in the `X-VP` header.
* **Permissions:**
  * Users can only send statements where they are the actor (or have delegated authority).
  * Users can typically only read statements where they are the actor. The DID in your JWT (`X-VP` header) must match the actor's DID you are querying for. A `401 Unauthorized` error often means a DID mismatch or an invalid/expired JWT.
* **Error Handling:** Always check response statuses and handle potential errors from the API or network issues.
* **Delegated Access:** For scenarios where another party needs to read or write statements on behalf of a user, LearnCloud supports a delegated access mechanism using Verifiable Credentials. (See [Delegated Access](/sdks/learncloud-storage-api/xapi-reference#delegated-access) for more info).
* **Contract-Scoped Statements:** When using ConsentFlow, xAPI statements can be automatically tagged with the contract URI, enabling queries by contract. (See [Contract-Scoped xAPI Statements](/sdks/learncloud-storage-api/xapi-reference#contract-scoped-xapi-statements) for details).
* **Voiding Statements:** You can invalidate previously sent statements. (See [Advanced Topics: Voiding Statements](/sdks/learncloud-storage-api/xapi-reference#voiding-statements) for how).

***

## Next Steps

Congratulations! You've now seen how to send, read, and view xAPI statements with LearnCloud.

From here, you can explore:

* Sending different types of xAPI statements (e.g., `completed`, `mastered`, with `result` objects).
* Using the other examples provided in our [xAPI Concepts Guide](/core-concepts/credentials-and-data/xapi-data).
* Implementing more advanced queries to filter and retrieve statements. (See [Advanced xAPI Statement Queries](/sdks/learncloud-storage-api/xapi-reference#advanced-xapi-statement-queries)).
* Using [Contract-Scoped xAPI Statements](/sdks/learncloud-storage-api/xapi-reference#contract-scoped-xapi-statements) to associate activity data with specific apps.
* Connecting these xAPI statements as evidence for Verifiable Credentials.

Happy tracking!


# Listen to Webhooks

**What are Webhooks in LearnCard?** Imagine you want your application to know *immediately* when something interesting happens to a user's LearnCard profile on the network – like receiving a new credential or a connection request. Instead of your app constantly asking "Anything new? Anything new?", the LearnCloud Network can send your application a direct message (a "notification") as soon as that event occurs. This direct message is sent to a specific web address (URL) that you provide, and this mechanism is called a **webhook**.

## **What you'll accomplish in this tutorial:**

1. Understand how LearnCloud notifications work at a high level.
2. Configure a profile on LearnCard to send notifications to your webhook URL.
3. Build a very simple web server (a "listener") to receive and process these notifications.
4. See an example of handling a "Connection Request" notification.

### **Why is this useful?**

Webhooks enable you to:

* Build responsive applications that react to events in real-time.
* Automate workflows based on LearnCloud Network activities.
* Enhance user experience by providing timely updates.

## **Prerequisites:**

1. **LearnCard SDK Initialized:** An active `learnCard` instance connected to the network (we'll call it `yourLearnCardInstance`).
2. **A Publicly Accessible URL:** Your application needs an endpoint (a URL) that the LearnCloud Network can reach over the internet to send `POST` requests.
   * **For local development:** Tools like **ngrok** are perfect for this! Ngrok can create a secure, public URL that tunnels to your local machine. We'll cover this.
   * **For production:** This would be a route on your deployed web server or a serverless function (e.g., AWS Lambda + API Gateway, Google Cloud Function).
3. **Basic understanding of:**
   * How webhooks work (HTTP `POST` requests).
   * Node.js and a simple web framework like Express (for our example listener). You can adapt the principles to any backend technology.
4. **Familiarity with LearnCard Profiles:** You'll be updating a profile to set its webhook URL.

***

## Part 1: How LearnCloud Notifications Work

Before we build, let's look at the flow (as shown in ["Notifications & Webhook Reference"](/sdks/learncard-network/notifications)):

```mermaid
sequenceDiagram
    participant App as "Client App"
    participant Brain as "LearnCloud Network API"
    participant SQS as "SQS Queue"
    participant Worker as "Lambda Worker"
    participant Webhook as "Your Webhook Listener"

    App->>Brain: Perform action (e.g., send boost to a user)
    Brain->>SQS: addNotificationToQueue()
    SQS->>Worker: notificationsWorker()
    Worker->>Worker: Parse notification
    Worker->>Webhook: sendNotification() HTTP POST request
    Note right of Webhook: Your app receives the data!
    Webhook-->>Worker: Acknowledge notification (e.g., HTTP 200 OK)
```

When an event occurs (like `App` sending a boost to a user whose profile has a webhook configured), the LearnCloud Network API eventually triggers an HTTP `POST` request to the registered webhook URL. Your application needs to be listening at that URL.

***

## Part 2: Setting Your Webhook URL in LearnCard

For a LearnCard profile to receive notifications via webhook, you need to tell LearnCard *where* to send them.

### **Step 2.1: Get Your Public Webhook URL**

* **If deploying to a server:** You'll have a public URL like `https://yourapp.com/api/learncard-webhook`.
* **For Local Development (using ngrok):**
  1. Install ngrok: <https://ngrok.com/download>
  2. If your local listener will run on port 3000 (we'll set this up later), run: `ngrok http 3000`
  3. Ngrok will give you a public "Forwarding" URL (e.g., `https://xxxx-yyy-zzz.ngrok.io`). **This is your temporary public webhook URL.** Use the `https` version.

### **Step 2.2: Update Your LearnCard Profile**

Use the LearnCard SDK to update the profile for which you want to receive notifications. Set its `notificationsWebhook` field to your public URL.

```typescript
// Ensure yourLearnCardInstance is initialized and authenticated
// for the profile you want to configure.

async function setWebhookUrl(webhookUrl: string) {
    try {
        const profileUpdateData = {
            notificationsWebhook: webhookUrl,
        };

        const success = await yourLearnCardInstance.invoke.updateProfile(profileUpdateData);

        if (success) {
            console.log(`Successfully updated profile's webhook URL to: ${webhookUrl}`);
        } else {
            console.error('Failed to update profile with webhook URL.');
        }
    } catch (error) {
        console.error('Error setting webhook URL:', error);
    }
}

// Replace with your actual ngrok URL or deployed webhook URL
const myPublicWebhookUrl = 'https://YOUR_NGROK_OR_DEPLOYED_URL.io/learncard-notifications'; 
// setWebhookUrl(myPublicWebhookUrl); // Call this function once to set it up
```

{% hint style="success" %}
**Action:** Run a script with this function call (or integrate it into your app's profile settings) to update the profile you want to receive notifications for.
{% endhint %}

***

## Part 3: Building a Simple Webhook Listener

Now, let's create a simple server that will listen for incoming notifications at the URL you just configured. We'll use Node.js and Express.

### **Step 3.1: Project Setup**

```bash
mkdir learncard-webhook-listener
cd learncard-webhook-listener
npm init -y
npm install express body-parser
# If using TypeScript (optional, but recommended)
# npm install typescript @types/express @types/node ts-node --save-dev
# npx tsc --init 
```

### **Step 3.2: Create Your Server (`listener.js` or `listener.ts`)**

```typescript
// listener.ts (or listener.js if not using TypeScript)
import express from 'express';
import bodyParser from 'body-parser';

const app = express();
const port = 3000; // The port ngrok will forward to

// Middleware to parse JSON request bodies
app.use(bodyParser.json());

// This is your webhook endpoint. 
// Make sure the path matches what you set in myPublicWebhookUrl 
// (e.g., if URL is https://.../learncard-notifications, path is /learncard-notifications)
app.post('/learncard-notifications', (req, res) => {
    console.log('Received a notification!');
    
    // The actual notification data is in req.body
    const notificationPayload = req.body;
    console.log('Payload:', JSON.stringify(notificationPayload, null, 2));

    // --- Process the notification based on its type ---
    if (notificationPayload.type === 'CONNECTION_REQUEST') {
        const fromProfile = notificationPayload.from; // This is an LCNProfile object
        const message = notificationPayload.message;
        console.log(`Received CONNECTION_REQUEST from: ${fromProfile?.displayName || fromProfile?.profileId || 'Unknown'}`);
        console.log(`Message: ${message?.body}`);
        
        // Example action: Log it, send an internal alert, update your database, etc.
        // For now, we just log it.
    } else if (notificationPayload.type === 'CREDENTIAL_RECEIVED') {
        const fromProfile = notificationPayload.from;
        const credentialUris = notificationPayload.data?.vcUris;
        console.log(`Received CREDENTIAL_RECEIVED from: ${fromProfile?.displayName || 'Unknown'}`);
        console.log(`Credential URIs:`, credentialUris);
        // You might want to fetch these credentials using learnCard.read.get(uri)
    } else {
        console.log(`Received unhandled notification type: ${notificationPayload.type}`);
    }

    // --- IMPORTANT: Acknowledge receipt quickly! ---
    // Send a 200 OK response to LearnCloud to let it know you received the notification.
    // If LearnCloud doesn't get a quick 2xx response, it might retry sending,
    // leading to duplicate processing.
    res.status(200).send('Notification received');

    // Any long-running tasks based on the notification should be done asynchronously
    // AFTER sending this response (e.g., queue it for later processing).
});

app.listen(port, () => {
    console.log(`Webhook listener started on http://localhost:${port}`);
    console.log(`If using ngrok, ensure it's forwarding to this port.`);
    console.log(`Your webhook endpoint is POST http://localhost:${port}/learncard-notifications`);
});
```

### **Step 3.3: Running Your Listener**

* If using JavaScript: `node listener.js`
* If using TypeScript: `npx ts-node listener.ts`

And if you're developing locally, make sure `ngrok http 3000` (or your chosen port) is running in another terminal.

***

## Part 4: Triggering and Testing Your Webhook

Now, let's test it! We'll simulate an action that sends a `CONNECTION_REQUEST` notification to the profile whose webhook you configured.

### **Step 4.1: Perform an Action**

You'll need another LearnCard instance (let's call it `profileA_learnCard`) to act as the requester. The profile you configured with the webhook URL will be `profileB_learnCard` (represented by `yourLearnCardInstance` in Step 2.2).

```typescript
// In a separate script or part of your testing setup:
// Assume profileA_learnCard is initialized for Profile A
// Assume profileB_ProfileId is the profileId of the user/service you configured the webhook for in Part 2.

// const profileB_ProfileId = 'the-profile-id-with-webhook-configured';

// async function sendConnectionRequest() {
//   try {
//     console.log(`Profile A attempting to connect with ${profileB_ProfileId}`);
//     const success = await profileA_learnCard.invoke.connectWith(profileB_ProfileId);
//     if (success) {
//       console.log('Connection request sent successfully by Profile A!');
//     } else {
//       console.error('Failed to send connection request from Profile A.');
//     }
//   } catch (error) {
//     console.error('Error sending connection request:', error);
//   }
// }

// sendConnectionRequest();
```

{% hint style="success" %}
**Action:** Execute code similar to the `sendConnectionRequest` function above, where `profileA_learnCard` sends a connection request to the profile that has the webhook set up.
{% endhint %}

### **Step 4.2: Check Your Listener's Logs**

If everything is set up correctly:

1. Profile A sends the connection request.
2. LearnCloud Network processes this and identifies that the target profile (Profile B) has a webhook URL.
3. LearnCloud Network sends a `POST` request to your ngrok URL (which forwards to `http://localhost:3000/learncard-notifications`).
4. Your `listener.js` / `listener.ts` server will receive the request.

You should see output in your listener's console similar to:

```json
Webhook listener started on http://localhost:3000
If using ngrok, ensure it's forwarding to this port.
Your webhook endpoint is POST http://localhost:3000/learncard-notifications
Received a notification!
Payload: {
  "type": "CONNECTION_REQUEST",
  "to": { /* Profile B's details */ },
  "from": { /* Profile A's details */ },
  "message": {
    "title": "New Connection Request",
    "body": "Profile A DisplayName has sent you a connection request!"
  }
}
Received CONNECTION_REQUEST from: Profile A DisplayName
Message: Profile A DisplayName has sent you a connection request!
```

***

## Important Considerations

* **Security:** Always use `https` for your webhook URLs in production. Ngrok provides this automatically.
* **Asynchronous Processing:** As mentioned, respond with `200 OK` quickly. If you need to do significant processing (like database updates, sending other API calls), do it after sending the response, perhaps by adding the task to an internal queue.
* **Error Handling & Retries:** Build robust error handling in your listener. Be aware that LearnCloud might retry sending a notification if it doesn't receive a timely success response. Design your processing to be **idempotent** (processing the same notification multiple times doesn't cause unintended side effects).
* **Payload Reference:** This tutorial focused on `CONNECTION_REQUEST`. Refer to the [LearnCloud Network API Notifications Documentation](/sdks/learncard-network/notifications) for the structure of all other notification types (`CREDENTIAL_RECEIVED`, `CONSENT_FLOW_TRANSACTION`, etc.) and expand your listener to handle them as needed.

***

## Summary & Next Steps

You've now learned how to:

1. **Configure a webhook URL** for a LearnCard profile.
2. **Build a basic webhook listener** using Node.js and Express.
3. **Receive and parse** an example `CONNECTION_REQUEST` notification.
4. **Trigger a test notification.**

From here, you can:

* Expand your listener to handle various other `type` values from the LearnCloud Network.
* Integrate more complex business logic into your webhook handler.
* Deploy your listener to a robust server or serverless environment for production use.

Happy listening!


# Verify Credentials

Tutorial: Verify a Verifiable Credential

This tutorial walks you through verifying a Verifiable Credential (VC) using LearnCard. Verification checks that:

1. The credential's cryptographic proof is valid
2. The credential hasn't been tampered with
3. The credential hasn't expired

## Prerequisites

* Node.js (v18+)
* Basic familiarity with [Verifiable Credentials](/core-concepts/credentials-and-data/verifiable-credentials-vcs)

## Installation

```bash
npm install @learncard/init
```

## Basic Verification

```typescript
import { initLearnCard } from '@learncard/init';

// Initialize LearnCard (no seed needed for verification-only)
const learnCard = await initLearnCard();

// Example signed credential (you'd receive this from an issuer)
const signedCredential = {
  "@context": ["https://www.w3.org/2018/credentials/v1"],
  "type": ["VerifiableCredential"],
  "issuer": "did:key:z6MkjZ...",
  "issuanceDate": "2024-01-01T00:00:00Z",
  "credentialSubject": {
    "id": "did:key:z6Mkp...",
    "achievement": "Completed Tutorial"
  },
  "proof": {
    "type": "Ed25519Signature2020",
    // ... proof details
  }
};

// Verify the credential
const result = await learnCard.invoke.verifyCredential(signedCredential);

console.log(result);
// { checks: ['proof', 'expiration'], warnings: [], errors: [] }
```

## Understanding Results

### Valid Credential

```typescript
const result = await learnCard.invoke.verifyCredential(validCredential);

if (result.errors.length === 0) {
  console.log('✅ Credential is valid!');
  console.log('Checks passed:', result.checks);
} else {
  console.log('❌ Credential is invalid');
  console.log('Errors:', result.errors);
}
```

### Human-Readable Output

For a more detailed, human-readable result, pass `true` as the third argument:

```typescript
const result = await learnCard.invoke.verifyCredential(signedCredential, {}, true);

console.log(result);
// [
//   { status: "Success", check: "proof", message: "Valid" },
//   { status: "Success", check: "expiration", message: "Valid • Does Not Expire" }
// ]
```

### Handling Invalid Credentials

```typescript
// Tampered credential (modified after signing)
const tamperedCredential = { ...signedCredential };
tamperedCredential.credentialSubject.achievement = "Fake Achievement";

const result = await learnCard.invoke.verifyCredential(tamperedCredential);

console.log(result);
// {
//   checks: [],
//   warnings: [],
//   errors: ['signature error: Verification equation was not satisfied']
// }
```

## Complete Example

```typescript
import { initLearnCard } from '@learncard/init';

async function verifyCredentialFromIssuer(credential: any) {
  const learnCard = await initLearnCard();
  
  const result = await learnCard.invoke.verifyCredential(credential, {}, true);
  
  const isValid = result.every(check => check.status === 'Success');
  
  if (isValid) {
    console.log('✅ Credential verified successfully!');
    result.forEach(check => {
      console.log(`  ${check.check}: ${check.message}`);
    });
  } else {
    console.log('❌ Credential verification failed:');
    result.forEach(check => {
      if (check.status === 'Failed') {
        console.log(`  ${check.check}: ${check.details}`);
      }
    });
  }
  
  return isValid;
}

// Usage
const credential = /* ... received from issuer ... */;
await verifyCredentialFromIssuer(credential);
```

## Next Steps

* Learn about [Verifiable Presentations](/core-concepts/credentials-and-data/verifiable-credentials-vcs) for sharing credentials
* Explore [Trust Registries](/core-concepts/identities-and-keys/trust-registries) for validating issuers


# LearnCard Wallet SDK

Quick Start Guide

**LearnCard Wallet SDK** is the fundamental component of the LearnCard ecosystem, providing core functionality for working with verifiable credentials (VCs) and decentralized identifiers (DIDs). It serves as the foundation upon which the entire LearnCard system is built, offering a plugin system for extending functionality and standardized interfaces for common operations.

## Install the SDK

Install using the package manager of your choice:

{% tabs %}
{% tab title="Bun" %}

```bash
bun add @learncard/init
```

{% endtab %}

{% tab title="yarn" %}

```bash
yarn add @learncard/init
```

{% endtab %}

{% tab title="npm" %}

```bash
npm i @learncard/init
```

{% endtab %}
{% endtabs %}

## Create, Issue, and Verify a Credential

To make your first wallet, import and call `initLearnCard` with a unique string that is 64 characters or less:

```typescript
import { initLearnCard } from '@learncard/init';

// Generate a random key for wallet seed
const seed = Array.from(crypto.getRandomValues(new Uint8Array(32)), dec =>
    dec.toString(16).padStart(2, '0')
).join('');

/** Or, if in node environment:
    const seed = crypto.randomBytes(32).toString('hex');
**/

// Initialize a new LearnCard wallet with a unique string that is 64 characters or less
const learnCard = await initLearnCard({ seed });

// Returns an unsigned, achievement credential in the OBv3 spec.
const unsignedAchievementCredential = learnCard.invoke.newCredential({ type: 'achievement' });

// Sign the Credential with your LearnCard issuer DID
const signedVc = await learnCard.invoke.issueCredential(unsignedAchievementCredential);

// Verify the Credential
const result = await learnCard.invoke.verifyCredential(signedVc, {}, true);
console.log(result);
// [
//     { status: "Success", check: "proof", message: "Valid" },
//     {
//         status: "Success",
//         check: "expiration",
//         message: "Valid • Does Not Expire"
//     }
// ]
```


# Authentication

The LearnCard Wallet SDK employs a decentralized authentication model rooted in [Decentralized Identifiers (DIDs)](/core-concepts/identities-and-keys/decentralized-identifiers-dids). As a developer, you initiate and control your digital identity through a securely generated [**Key Generation Seed**](/core-concepts/identities-and-keys/seed-phrases).

Here's the core concept:

1. **You generate a seed**: This seed must have sufficient entropy (randomness).
2. **SDK creates keypairs**: The SDK uses this seed to deterministically create cryptographic keypairs (a public and private key). These keypairs are the foundation of your digital identity.
3. **Authenticate using DIDs**: These keypairs are represented as [DIDs](/core-concepts/identities-and-keys/decentralized-identifiers-dids). The SDK uses these DIDs and their associated private keys to authenticate with various services like the LearnCloud Network API, Storage APIs, and AI services through a process called DID-Auth.

This means you prove your identity by signing challenges with your private key, never revealing the key itself.

## Getting Started: Generating Your Identity Seed

Authentication begins with a **Key Generation Seed**. This is a crucial piece of data: a **64-character hexadecimal string**, which represents 32 bytes of secure randomness.

{% hint style="warning" %}
**Your Responsibility**: You are responsible for generating this seed and ensuring its secure management. The security of your LearnCard identity hinges on the secrecy and integrity of this seed.
{% endhint %}

### **How to Generate a** [**Secure Seed**](/core-concepts/identities-and-keys/seed-phrases):

Use a cryptographically secure random number generator to create 32 bytes of data and then convert it to a 64-character hexadecimal string.

* **In a Browser Environment:**

  ```typescript
  const randomKeyHex = Array.from(crypto.getRandomValues(new Uint8Array(32)), dec =>
      dec.toString(16).padStart(2, '0')
  ).join('');
  // randomKeyHex will be a 64-character hexadecimal string like "1a2b3c..."
  ```
* **In a Node.js Environment:**

  ```typescript
  import crypto from 'node:crypto';

  const randomKeyHex = crypto.randomBytes(32).toString('hex');
  // randomKeyHex will be a 64-character hexadecimal string
  ```

{% hint style="danger" %}

## Key Security: Critical Reminder

Your 64-character hexadecimal **seed is the master key** for your LearnCard identity.

* **Protect It Rigorously**: Anyone who gains access to this seed can regenerate all your associated private keys. This would allow them to impersonate you, control your DIDs, and access or modify any data or credentials linked to your identity.
* **Irreversible Loss**: If you lose this seed and have no other backup of the private keys themselves, you may permanently lose access to your LearnCard identity and any associated assets or credentials.
* **Handling**: Treat this hex string with the same (or even greater) caution as you would a mnemonic seed phrase for a cryptocurrency wallet. Store it securely, preferably offline and in multiple locations if you are managing it directly.
  {% endhint %}

### **Initializing LearnCard with Your Seed**:

Once you have your 64-character hexadecimal seed, you use it to initialize the LearnCard SDK. This process generates the cryptographic keys tied to your identity.

```typescript
// Example: bun add @learncard/init
import { initLearnCard } from '@learncard/init';

async function initialize() {
    const seed = 'your64characterhexstringgoeshere...'; // Replace with your generated seed

    const learnCard = await initLearnCard({
        seed: seed,
        network: true,
    });

    // The `learnCard` instance is now ready for authenticated operations.
    return learnCard;
}

initialize().then(lc => console.log('LearnCard Initialized!', lc));
```

{% hint style="info" %}
**Important Notes on the Seed**:

* **Format**: The `seed` parameter for `initLearnCard` *must* be a hexadecimal string. Providing a non-hex string will result in an error.
* **Length**: If you provide a hexadecimal string that is shorter than 64 characters, `initLearnCard` will typically prefix it with zeroes until it reaches the required 64-character length. For example, `'abc'` would be treated as `'000...00abc'` (61 zeroes followed by 'abc').
  {% endhint %}

## Authentication Flow: How It Works

1. **Seed to Keys**: The 64-character hexadecimal `seed` you provide is the master input. The SDK uses it to deterministically derive one or more cryptographic keypairs. "Deterministic" means that if you use the same seed again, you will always get the exact same keypairs.
2. **Keys to DIDs**: These keypairs are then used to generate [Decentralized Identifiers (DIDs)](/core-concepts/identities-and-keys/decentralized-identifiers-dids). Your primary, locally generated DID will typically be a `did:key`. A DID is a globally unique identifier that you control, representing your digital identity.
3. **DID-Auth**: When your application needs to perform an action that requires authentication (e.g., accessing data, calling an API), the LearnCard SDK uses the private key associated with your DID. The API will issue an authentication challenge, which the SDK signs using your private key. This signature proves you control the DID without ever exposing the private key. This entire process is known as DID-Auth.

## Authenticating with Specific APIs

### 1. LearnCloud Network API

The [LearnCloud Network API ](/sdks/learncard-network)enables you to create and manage rich, DID-based user profiles.

#### **Initial Authentication & Profile Creation**:

1. **Authenticate with `did:key`**: Your first interaction with the Network API, such as creating a profile, will be authenticated using the `did:key` that the LearnCard SDK generated from your seed. This `did:key` serves as your initial, self-controlled digital signature.
2. **Create a Profile**: Once authenticated with your `did:key`, you can make a request to the Network API to create a user profile (e.g., a Regular Profile for an individual, or a Service Profile for an application).
3. **Receive `did:web`**: Upon successful creation of your profile on the LearnCloud Network, the service will typically associate your profile with a new, more publicly discoverable DID: a `did:web`. This `did:web` is tied to a domain name and represents your identity within the LearnCloud ecosystem.

**Conceptual Example**:

```typescript
// Assuming 'learnCard' is your initialized LearnCard instance from the previous step
async function createNetworkProfile(learnCard) {
    try {
        const learnCard = await initialize(); // Initialize LearnCard with seed & network = true

        // The SDK automatically uses your did:key for authentication in this step
        const profileData = {
            displayName: 'Alice Wonderland',
            profileId: 'alice-wonderland',
            // ... other desired profile attributes
        };

        const newProfile = await learnCard.invoke.createProfile(profileData);

        console.log('Profile created successfully:', newProfile);
        console.log('Your LearnCloud Network DID Web (did:web):', learnCard.id.did('web'));
        console.log('Your LearnCloud DID Key (did:key):', learnCard.id.did('key'));

        return newProfile;
    } catch (error) {
        console.error('Error creating profile:', error);
    }
}

// Example usage after initializing learnCard
// initialize().then(lc => createNetworkProfile(lc));
```

After profile creation, you can use your `did:web` (and in some cases, still your `did:key`) for ongoing interactions with the Network API.

### 2. Storage, AI, and Other APIs

Authentication with other APIs integrated into the LearnCard ecosystem (e.g., for decentralized storage, AI services) follows the same fundamental DID-Auth pattern:

* Your LearnCard instance, holding keys derived from your seed, will use the appropriate DID (e.g., `did:key`, `did:web`) to sign authentication challenges presented by these services.
* This proves your control over the identity requesting the action.

{% hint style="info" %}
Always refer to the specific documentation for each API to understand any unique requirements or recommended DIDs for authentication.
{% endhint %}

## Further Reading

For a more in-depth understanding of the concepts mentioned here, please refer to our Core Concept explainer documents:

* [Core Concept: Seeds](/core-concepts/identities-and-keys/seed-phrases)
* [Core Concept: DIDs (Decentralized Identifiers)](/core-concepts/identities-and-keys/decentralized-identifiers-dids)
* [Core Concept: Profiles](/core-concepts/identities-and-keys/network-profiles)
* [Understanding DID-Auth Specification](https://www.w3.org/Security/201812-Auth-ID/04_-_Day_1_-_Understanding_DID_Auth.pdf)


# Usage Examples

This page provides comprehensive examples for using the LearnCard SDK. If you're just getting started, we recommend beginning with the [Quick Start guide](/quick-start/your-first-integration).

## Table of Contents

| Section                                                          | Description                   |
| ---------------------------------------------------------------- | ----------------------------- |
| [Initialize SDK Client](#initialize-sdk-client)                  | Basic wallet initialization   |
| [Key Generation](#key-generation)                                | Generating secure seeds       |
| [Create Credentials](#create-credentials)                        | Building unsigned credentials |
| [Issue Credentials](#issue-credentials)                          | Signing credentials           |
| [Verify Credentials](#verify-credentials)                        | Validating credential proofs  |
| [Issue/Verify Presentations](#issueverify-presentations)         | Working with VPs              |
| [Storing/Retrieving Credentials](#storingretrieving-credentials) | URIs and storage              |
| [LearnCloud Network](#learncloud-network)                        | Profiles, connections, boosts |

***

## Initialize SDK Client

Depending on your use-case and specific needs, constructing a LearnCard is likely as simple as the following code:

```typescript
import { initLearnCard } from '@learncard/init';

const learnCard = await initLearnCard({ seed: 'abc123' });
```

#### The initLearnCard function

While there are many init functions that are exposed and that can be used, we recommend instead sticking to the `initLearnCard` function.

`initLearnCard` is a config-driven, heavily overloaded function, that allows you to construct a wallet flexibly, without sacrificing type safety.

Under the hood, it is simply a map between the config you provide and the init function you would normally need to call, meaning calls like `initLearnCard()` and `emptyLearnCard()` are identical.

## Example Usage

```typescript
import { initLearnCard } from '@learncard/init';

// Constructs an empty LearnCard without key material (can not sign VCs).
// Useful for Verifying Credentials only in a light-weight form.
const emptyLearncard = await initLearnCard();

// Constructs a LearnCard from a deterministic seed.
const learncard = await initLearnCard({ seed: 'abc123' });

// Constructs a LearnCard default connected to LearnCard Network hosted at https://network.learncard.com
const networkLearnCard = await initLearnCard({ seed: 'abc123', network: true });

// If you are calling guardian-gated Network routes from a managed/child profile,
// you can optionally provide a function that returns a guardian approval token (JWT VP).
// The Network client will attach it as an `x-guardian-approval` header when present.
const networkLearnCardWithGuardianApproval = await initLearnCard({
    seed: 'abc123',
    network: true,
    guardianApprovalGetter: async () => {
        return undefined;
    },
});

// Constructs a LearnCard default connected to VC-API at https://bridge.learncard.com for handling signing
const defaultApi = await initLearnCard({ vcApi: true });

// Constructs a LearnCard connected to a custom VC-API, with Issuer DID specified.
const customApi = await initLearnCard({ vcApi: 'vc-api.com', did: 'did:key:123' });

// Constructs a LearnCard connected to a custom VC-API that implements /did discovery endpoint.
const customApiWithDIDDiscovery = await initLearnCard({ vcApi: 'https://bridge.learncard.com' });

// Constructs a LearnCard with no plugins. Useful for building your own bespoke LearnCard
const customLearnCard = await initLearnCard({ custom: true });
```

The examples above are not exhaustive of possible ways to instantiate a LearnCard:

* For more on initialization with a VC-API, check out the [VC-API Plugin](https://github.com/learningeconomy/LearnCard/tree/main/docs/sdks/official-plugins/vc-api.md).

#### The learnCardFromSeed function

Helper function to quickly create a LearnCard from seed.

```typescript
import { learnCardFromSeed } from '@learncard/init';

const learnCard = await learnCardFromSeed('abc123');
```

#### The emptyLearnCard function

For a bare-bones LearnCard wallet without any plugins.

```typescript
import { emptyLearncard } from '@learncard/init';

const learnCard = await emptyLearnCard();
```

## Key Generation

{% hint style="danger" %}
**There be dragons here.** 🐉

In production environments, take great care and caution when generating and storing key material. Insufficient entropy or insecure storage, among other vectors, can easily compromise your data and identities.

**Warning:** Key input should be a hexadecimal string. If you pass a string that is not valid hex, an error will be thrown!
{% endhint %}

{% hint style="warning" %}
**Good to know:** If you do not pass in a string that is 64 characters long. It will be prefixed with zeroes until it is 64 characters. This means that '1' and '001' are identical keys in the eyes of `initLearnCard`.
{% endhint %}

How to generate and store keys is left to you, the consumer. However, if you'd like to simply generate a random key, you can do so with the following code:

{% tabs %}
{% tab title="Browser" %}

```typescript
const randomKey = Array.from(crypto.getRandomValues(new Uint8Array(32)), dec =>
    dec.toString(16).padStart(2, '0')
).join('');
```

{% endtab %}

{% tab title="Node" %}

```typescript
import crypto from 'node:crypto';

const randomKey = crypto.randomBytes(32).toString('hex');
```

{% endtab %}
{% endtabs %}

To speed up instantiation of the wallet, you can host our[ didkit](https://github.com/spruceid/didkit) wasm binary yourself.

{% tabs %}
{% tab title="Webpack 5" %}

<pre class="language-typescript"><code class="lang-typescript">// Make sure you have the didkit plugin installed! bun add @learncard/didkit-plugin

import { initLearnCard } from '@learncard/init';
<strong>import didkit from '@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm';
</strong>
const learnCard = await initLearnCard({ seed: 'abc123', didkit });
</code></pre>

{% endtab %}

{% tab title="Vite" %}

<pre class="language-typescript"><code class="lang-typescript">// Make sure you have the didkit plugin installed! bun add @learncard/didkit-plugin

import { initLearnCard } from '@learncard/init';
<strong>import didkit from '@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm?url';
</strong>
const learnCard = await initLearnCard({ seed: 'abc123', didkit });
</code></pre>

{% endtab %}
{% endtabs %}

If you're curious about what the above code is doing, read more[ here](https://github.com/learningeconomy/LearnCard/tree/main/docs/sdks/official-plugins/didkit.md).

## Create Credentials

### Test Credential

One of the easiest—and fastest—ways to create a credential is to generate a test credential:

```typescript
// Returns an unsigned, test credential in the OBv3 spec.
const unsignedVc = learnCard.invoke.getTestVc();
```

### Basic Boost Credential

In it's most basic form, you can create a Boost credential using the following schema:

```json
{
    "@context": [
        "https://www.w3.org/ns/credentials/v2",
        "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json",
        "https://ctx.learncard.com/boosts/1.0.3.json"
    ],
    "credentialSubject": {
        "achievement": {
            "achievementType": "ext:LCA_CUSTOM:Social Badge:Adventurer",
            "criteria": {
                "narrative": "This badge is awarded for being a adventurer."
            },
            "description": "An adventure badge.",
            "id": "urn:uuid:123",
            "image": "https://i.postimg.cc/s2xdx5Ss/erik-jan-leusink-Ib-Px-GLg-Ji-MI-unsplash.jpg",
            "name": "Adventurer",
            "type": ["Achievement"]
        },
        "id": "did:web:network.learncard.com:users:example",
        "type": ["AchievementSubject"]
    },
    "display": {
        "backgroundColor": "",
        "backgroundImage": "",
        "displayType": "badge"
    },
    "image": "https://i.postimg.cc/s2xdx5Ss/erik-jan-leusink-Ib-Px-GLg-Ji-MI-unsplash.jpg",
    "skills": [],
    "issuanceDate": "2025-04-01T16:56:00.667Z",
    "issuer": "did:web:network.learncard.com:users:issuer-example",
    "name": "Tabby Cat",
    "type": ["VerifiableCredential", "OpenBadgeCredential", "BoostCredential"]
}
```

### Credentials from Template

But sometimes the test credential is too basic for most use cases. That's why LearnCard has out-of-the-box support for some basic types of credentials, and a simple function to create new credentials:

```typescript
// Returns an unsigned, basic credential
const basicCredential = learnCard.invoke.newCredential({ type: 'basic' });
// Returns an unsigned, achievement credential.
const achievementCredential = learnCard.invoke.newCredential({ type: 'achievement' });
```

## Issue Credentials

First, make an unsigned [Verifiable Credential](https://www.w3.org/TR/vc-data-model/). You can do this yourself if you already have that set up for your app, or, if you just need to test out working with this library, you can use the `newCredential` method to easily create a test VC.

```typescript
const unsignedVc = learnCard.invoke.newCredential();
```

To sign (or "issue") that VC, simply call `issueCredential`

```typescript
const signedVc = await learnCard.invoke.issueCredential(unsignedVc);
```

## Verify Credentials

After a credential is signed, the credential may be transferred via an exchange mechanism, where a receiving party can verify it! To verify a signed Verifiable Credential, you can use `verifyCredential`

{% tabs %}
{% tab title="Valid Credential" %}

```typescript
const result = await learnCard.invoke.verifyCredential(signedVc);

console.log(result);
// { checks: ['proof', 'expiration'], warnings: [], errors: [] }

// OR, for a more human readable output:

const result = await learnCard.invoke.verifyCredential(signedVc, {}, true);
// [
//     { status: "Success", check: "proof", message: "Valid" },
//     {
//         status: "Success",
//         check: "expiration",
//         message: "Valid • Does Not Expire"
//     }
// ]
```

{% endtab %}

{% tab title="Invalid Credential" %}

```typescript
signedVc.expirationDate = '2022-06-10T18:26:57.687Z';

const result = await learnCard.invoke.verifyCredential(signedVc);

console.log(result);
// {
//   checks: ['proof'],
//   warnings: [],
//   errors: [
//     'signature error: Verification equation was not satisfied',
//     'expiration error: Credential is expired'
//   ]
// }

// OR, for a more human readable output:

const result = await learnCard.invoke.verifyCredential(signedVc, {}, true);

console.log(result);
// [
//     {
//         status: "Failed",
//         check: "signature",
//         details: "signature error: Verification equation was not satisfied"
//     },
//     {
//         status: "Failed",
//         check: "expiration",
//         details: "Invalid • Expired 10 JUN 2022"
//     },
//     { status: "Success", check: "proof", message: "Valid" }
// ]
```

{% endtab %}
{% endtabs %}

## Issue/Verify Presentations

Similar to Verifiable Credentials, LearnCard has methods for verifying and issuing Verifiable Presentations:

{% tabs %}
{% tab title="Valid Presentation" %}

```typescript
const unsignedVp = await learnCard.invoke.getTestVp();

//Package signed Verifiable Credential into the presentation
unsignedVp.verifiableCredential = signedVc;

const vp = await learnCard.invoke.issuePresentation(unsignedVp);

const result = await learnCard.invoke.verifyPresentation(vp);

console.log(result);
// {
//   checks: ['proof'],
//   warnings: [],
//   errors: [],
// }
```

{% endtab %}

{% tab title="Invalid Presentation" %}

```typescript
const unsignedVp = await learnCard.invoke.getTestVp();
const vp = await learnCard.invoke.issuePresentation(unsignedVp);

vp.holder = 'did:key:nope';

const result = await learnCard.invoke.verifyPresentation(vp);

console.log(result);
// {
//     checks: [],
//     warnings: [],
//     errors: ['Unable to filter proofs: Unable to resolve: invalidDid'],
// }
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
**What is a** [**Verifiable Presentation**](https://www.w3.org/TR/vc-data-model/#dfn-presentations)**, and why would I use one?**

Verifiable Presentations enable trusted sharing of one or more claims in a single, verifiable package. Claims may be bundled from one or multiple issuers, and they may consist of the original Verifiable Credential, or a derived "zero-knowledge proof."

**As a general principle,** you should use Verifiable Presentations when *presenting* Verifiable Credentials to a verifying party because it proves the relationship between the user or entity presenting the credential, and the credential itself.
{% endhint %}

## Storing/Retrieving Credentials

Credentials can be converted back and forth to [URIs](/core-concepts/credentials-and-data/uris), which can be stored per holder using [Control Planes](/core-concepts/architecture-and-principles/control-planes). [URIs](/core-concepts/credentials-and-data/uris) simplify complex processes, such as indexing and caching, over credentials stored in many different locations, such as in IPFS, device storage, or a Decentralized Web Node.

{% code title="Issuer" %}

```typescript
const holderDid = 'did:key:z6MknqnHBn4Rx64gH4Dy1qjmaHjxFjaNG1WioKvQuXKhEKL5';
const uvc = learnCard.invoke.newCredential({ subject: holderDid });
const vc = await learnCard.invoke.issueCredential(uvc);
const uri = await learnCard.store.LearnCloud.uploadEncrypted(vc);

// *** Send URI to Holder ***
```

{% endcode %}

{% code title="Holder" %}

```typescript
// *** Receive URI from Issuer ***

const credential = await learnCard.read.get(uri);
const result = await learnCard.invoke.verifyCredential(credential);

if (result.errors.length == 0) {
    await learnCard.index.LearnCloud.add({ uri, id: 'test' });
}

// Later, when the Holder would like to see the credential again
const records = await learnCard.index.LearnCloud.get();
const record = records.find(({ id }) => id === 'test');
const storedCredential = await learnCard.read.get(record.uri);

// _.isEqual(credential, storedCredential) = true
```

{% endcode %}

{% hint style="info" %}
The above example uses LearnCloud storage, but there are many ways to store and retrieve a credential! Check out the[ **Store control plane**](/core-concepts/architecture-and-principles/control-planes#store-control-plane) for more info and options.
{% endhint %}

## LearnCloud Network

### **Create a Profile**

To create a new profile, use the `createProfile` method. This method accepts an object containing the profile information, excluding the `did` and `isServiceProfile` properties.

```javascript
await learnCard.invoke.createProfile({
    displayName: 'John Smith',
    profileId: 'johnsmith',
    image: 'https://example.com/avatar.jpg',
});
```

These examples demonstrate creating, retrieving, updating, and deleting different types of profiles and profile managers.

```typescript
// --- Creating Profiles ---
const profileDetails = {
    profileId: 'john.doe',
    displayName: 'John Doe',
    email: 'john.doe@example.com',
    // ... other LCNProfile fields
};

const managerDetails = {
    managerDid: 'did:example:manager123',
    // ... other LCNProfileManager fields
};

try {
    // Create a regular user profile
    const regularProfileDid = await learnCard.invoke.createProfile(profileDetails);
    console.log('Regular Profile DID:', regularProfileDid);

    // Create a service profile
    const serviceProfileDid = await learnCard.invoke.createServiceProfile(profileDetails);
    console.log('Service Profile DID:', serviceProfileDid);

    // Create a managed profile (often used for profiles requiring oversight)
    const managedProfileDid = await learnCard.invoke.createManagedProfile(profileDetails);
    console.log('Managed Profile DID:', managedProfileDid);

    // Create a managed service profile
    const managedServiceProfileDid = await learnCard.invoke.createManagedServiceProfile(
        profileDetails
    );
    console.log('Managed Service Profile DID:', managedServiceProfileDid);

    // Create a profile manager
    const profileManagerId = await learnCard.invoke.createProfileManager(managerDetails);
    console.log('Profile Manager ID:', profileManagerId);

    // Create a child profile manager under an existing parent
    const parentManagerUri = 'uri:manager:parent123'; // Replace with actual parent URI
    const childProfileManagerId = await learnCard.invoke.createChildProfileManager(
        parentManagerUri,
        managerDetails
    );
    console.log('Child Profile Manager ID:', childProfileManagerId);
} catch (error) {
    console.error('Error creating profile:', error);
}
```

### Retrieving & Searching Profiles <a href="#retrieving-profiles" id="retrieving-profiles"></a>

To search for profiles, use the `searchProfiles` method. This method accepts an optional `profileId` parameter and an `options` object. The `options` object can contain the following properties:

* `limit`: Maximum number of profiles to return.
* `includeSelf`: Whether to include the current user's profile in the results.
* `includeConnectionStatus`: Whether to include connection status in the results.

```javascript
const profileId = 'johnsmith';
const options = { limit: 10, includeSelf: false, includeConnectionStatus: true };

learnCard.invoke.searchProfiles(profileId, options);
```

More advanced usage:

```typescript
// --- Retrieving & Searching Profiles ---
const queryOptionsMock = { limit: 10, cursor: undefined }; // Example pagination
const profileQueryMock = { displayName: 'John' }; // Example query

try {
    // Get the current authenticated user's profile
    const myProfile = await learnCard.invoke.getProfile(); // No argument for self
    console.log('My Profile:', myProfile);

    // Get another profile by its ID
    const otherProfileId = 'jane.doe'; // Example profile ID
    const otherProfile = await learnCard.invoke.getProfile(otherProfileId);
    console.log(`Profile for ${otherProfileId}:`, otherProfile);

    // Get profile manager details
    const someManagerId = 'manager-xyz'; // Example manager ID
    const profileManager = await learnCard.invoke.getProfileManagerProfile(someManagerId);
    console.log('Profile Manager:', profileManager);

    // Search for profiles
    const searchResults = await learnCard.invoke.searchProfiles('john', {
        limit: 5,
        includeConnectionStatus: true,
    });
    console.log('Search Results for "john":', searchResults);

    // Get profiles available to the current user (e.g., owned or managed)
    const availableProfiles = await learnCard.invoke.getAvailableProfiles({
        query: profileQueryMock,
        ...queryOptionsMock,
    });
    console.log('Available Profiles:', availableProfiles);

    // Get profiles managed by the current user
    const managedProfiles = await learnCard.invoke.getManagedProfiles({
        query: profileQueryMock,
        ...queryOptionsMock,
    });
    console.log('Managed Profiles:', managedProfiles);

    // Get service profiles managed by the user (or by a specific manager ID if provided)
    const managerIdForServiceProfiles = 'manager-abc';
    const managedServiceProfiles = await learnCard.invoke.getManagedServiceProfiles({
        id: managerIdForServiceProfiles,
        ...queryOptionsMock,
    });
    console.log(
        `Managed Service Profiles for manager ${managerIdForServiceProfiles}:`,
        managedServiceProfiles
    );
} catch (error) {
    console.error('Error retrieving profiles:', error);
}
```

### Updating & Deleting Profiles <a href="#retrieving-profiles" id="retrieving-profiles"></a>

```typescript
// --- Updating & Deleting Profiles ---
const profileUpdates = {
    displayName: 'Johnathan Doe',
    bio: 'Updated bio information.',
    // ... other fields to update
};

const managerUpdates = {
    displayName: 'Senior Manager',
    // ... other fields
};

try {
    // Update the current authenticated user's profile
    // Note: updateProfile typically updates the profile associated with the current learnCard instance's DID
    const updateSuccess = await learnCard.invoke.updateProfile(profileUpdates);
    console.log('Profile update success:', updateSuccess);

    // Update a profile manager's profile
    // Assuming managerUpdates includes the manager's identifier or is for the current profile manager context
    const managerUpdateSuccess = await learnCard.invoke.updateProfileManagerProfile(managerUpdates);
    console.log('Manager profile update success:', managerUpdateSuccess);

    // Delete the current authenticated user's profile
    // Be very careful with this operation!
    // const deleteSuccess = await learnCard.invoke.deleteProfile();
    // console.log('Profile delete success:', deleteSuccess);
} catch (error) {
    console.error('Error updating/deleting profile:', error);
}
```

### Connection Management

#### **Connect with a Profile**

To send a connection request to another profile, use the `connectWith` method. This method accepts a `profileId` parameter.

```javascript
const profileId = 'janesmith';

await learnCard.invoke.connectWith(profileId);
```

#### **Generate an Invite**

To generate an invite, use the `generateInvite` method. This method now accepts two parameters: a `challenge` and an `expiration` parameter. The `challenge` parameter is optional and will be automatically generated if not provided. The `expiration` parameter sets the duration (in seconds) for which the invite remains valid and defaults to 30 days if not specified.

```javascript
const challenge = 'your_challenge'; // Custom challenge string (optional)
const expiration = 3600 * 24 * 7; // Invitation expires in 7 days (optional)

await learnCard.invoke.generateInvite({ challenge, expiration });
```

#### **Connect with a Profile using an Invite**

To connect with another profile using an invite, use the `connectWithInvite` method. This method requires a `profileId` and a `challenge` parameter.

<pre class="language-javascript"><code class="lang-javascript">const profileId = 'janesmith';
const challenge = 'your_challenge';

<strong>await learnCard.invoke.connectWithInvite(profileId, challenge);
</strong></code></pre>

#### **Accept a Connection Request**

To accept a connection request from another profile, use the `acceptConnectionRequest` method. This method accepts a `profileId` parameter.

```javascript
const profileId = 'janesmith';

await learnCard.invoke.acceptConnectionRequest(profileId);
```

Advanced examples for establishing, managing, and retrieving connections between profiles.

```typescript
const targetProfileId = 'jane.doe.connections'; // Example target profile ID for connection
const connectionRequestProfileId = 'john.wayne.connections'; // Example profile ID of an incoming connection request

try {
    // --- Connection Operations ---
    // Send a connection request
    const connectSuccess = await learnCard.invoke.connectWith(targetProfileId);
    console.log(`Connection request to ${targetProfileId} success:`, connectSuccess);

    // Accept an incoming connection request
    const acceptSuccess = await learnCard.invoke.acceptConnectionRequest(
        connectionRequestProfileId
    );
    console.log(`Accepted connection request ${connectionRequestProfileId}:`, acceptSuccess);

    // Disconnect with a connected profile
    const disconnectSuccess = await learnCard.invoke.disconnectWith(targetProfileId);
    console.log(`Disconnected from ${targetProfileId}:`, disconnectSuccess);

    // Cancel an outgoing connection request you previously sent
    const cancelSuccess = await learnCard.invoke.cancelConnectionRequest(targetProfileId);
    console.log(`Cancelled connection request to ${targetProfileId}:`, cancelSuccess);

    // --- Connection Invitations ---
    // Generate an invitation
    const inviteDetails = await learnCard.invoke.generateInvite('optional-challenge-string', 3600); // Expires in 1 hour
    console.log('Generated Invite:', inviteDetails);
    // (Off-band: share inviteDetails.profileId and inviteDetails.challenge with another user)

    // Another user connects using the invitation
    // This would typically be called by a different learnCard instance/user
    // const connectingProfileId = inviteDetails.profileId;
    // const challengeFromInvite = inviteDetails.challenge;
    // const connectInviteSuccess = await otherLearnCard.invoke.connectWithInvite(connectingProfileId, challengeFromInvite);
    // console.log('Connected via invite:', connectInviteSuccess);

    // --- Retrieving Connections ---
    const paginationOptions = { limit: 10 };

    const connections = await learnCard.invoke.getPaginatedConnections(paginationOptions);
    console.log('My Connections:', connections.records);

    const pendingRequests = await learnCard.invoke.getPaginatedPendingConnections(
        paginationOptions
    );
    console.log('My Pending Outgoing Requests:', pendingRequests.records);

    const incomingRequests = await learnCard.invoke.getPaginatedConnectionRequests(
        paginationOptions
    );
    console.log('My Incoming Connection Requests:', incomingRequests.records);

    // --- Blocking Profiles ---
    const profileToBlock = 'annoying.user';
    const blockSuccess = await learnCard.invoke.blockProfile(profileToBlock);
    console.log(`Blocked ${profileToBlock}:`, blockSuccess);

    const blockedProfiles = await learnCard.invoke.getBlockedProfiles();
    console.log('Blocked Profiles:', blockedProfiles);

    // Unblock a profile
    // const unblockSuccess = await learnCard.invoke.unblockProfile(profileToBlock);
    // console.log(`Unblocked ${profileToBlock}:`, unblockSuccess);
} catch (error) {
    console.error('Error in connection management:', error);
}
```

### Credential & Presentation Exchange <a href="#retrieving-profiles" id="retrieving-profiles"></a>

Demonstrates sending, receiving, and managing Verifiable Credentials (VCs) and Verifiable Presentations (VPs).

#### **Send a Credential**

To send a credential to another profile, use the `sendCredential` method. This method accepts a `profileId`, a `vc` object (which can be an `UnsignedVC` or `VC`), and an optional `encrypt` parameter.

<pre class="language-javascript"><code class="lang-javascript">const profileId = 'janesmith';
const vc = await networkLearnCard.invoke.issueCredential(networkLearnCard.invoke.newCredential())
const encrypt = true;

<strong>await learnCard.invoke.sendCredential(profileId, vc, encrypt);
</strong></code></pre>

#### **Get Received Credentials**

To retrieve all received credentials, use the `getReceivedCredentials` method. This method accepts an optional `from` parameter.

```javascript
const from = 'johnsmith';

await learnCard.invoke.getReceivedCredentials(from);
```

#### **Accept a Credential**

To accept a credential, use the `acceptCredential` method. This method accepts a `uri` parameter.

<pre class="language-javascript"><code class="lang-javascript">const uri = 'your_credential_uri';
<strong>await learnCard.invoke.acceptCredential(uri);
</strong></code></pre>

#### **Send a Presentation**

To send a presentation to another profile, use the `sendPresentation` method. This method accepts a `profileId`, a `vp` object, and an optional `encrypt` parameter.

```javascript
const profileId = 'janesmith';
const vp = your_presentation;
const encrypt = true;

await learnCard.invoke.sendPresentation(profileId, vp, encrypt);
```

#### Advanced Examples:

```typescript
// Assume vcMock and vpMock are properly structured Verifiable Credential and Presentation objects
const vcMock = await learnCard.invoke.issueCredential(learnCard.invoke.getTestVc());
const vpMock = await learnCard.invoke.issuePresentation(await learnCard.invoke.getTestVp());

const recipientProfileId = 'bob.the.receiver';
const credentialUriToManage = 'uri:credential:xyz789'; // Example URI of a received/sent credential

try {
    // --- Sending Credentials & Presentations ---
    const sentCredentialUri = await learnCard.invoke.sendCredential(
        recipientProfileId,
        vcMock,
        true
    ); // Encrypt = true
    console.log('Sent Credential URI:', sentCredentialUri);

    const sentPresentationUri = await learnCard.invoke.sendPresentation(
        recipientProfileId,
        vpMock,
        true
    ); // Encrypt = true
    console.log('Sent Presentation URI:', sentPresentationUri);

    // --- Accepting Credentials & Presentations ---
    // (This would typically be called by the recipient's learnCard instance)
    // const receivedCredentialUri = 'uri:credential:abc123'; // URI from notification or shared link
    // const acceptCredentialSuccess = await recipientLearnCard.invoke.acceptCredential(receivedCredentialUri);
    // console.log('Accepted Credential:', acceptCredentialSuccess);

    // const receivedPresentationUri = 'uri:presentation:def456';
    // const acceptPresentationSuccess = await recipientLearnCard.invoke.acceptPresentation(receivedPresentationUri);
    // console.log('Accepted Presentation:', acceptPresentationSuccess);

    // --- Retrieving Information ---
    // Assuming 'did:example:sender' is the DID of a profile that sent you credentials
    const receivedCredentials = await learnCard.invoke.getReceivedCredentials('did:example:sender');
    console.log('Received Credentials:', receivedCredentials);

    const sentCredentials = await learnCard.invoke.getSentCredentials(recipientProfileId);
    console.log('Sent Credentials:', sentCredentials);

    const incomingCredentials = await learnCard.invoke.getIncomingCredentials(); // Get all pending incoming
    console.log('Incoming Credentials (pending acceptance):', incomingCredentials);

    // Similar retrieval for presentations
    const receivedPresentations = await learnCard.invoke.getReceivedPresentations(
        'did:example:sender'
    );
    console.log('Received Presentations:', receivedPresentations);

    // --- Deleting ---
    // const deleteCredentialSuccess = await learnCard.invoke.deleteCredential(credentialUriToManage);
    // console.log('Deleted Credential:', deleteCredentialSuccess);

    // const deletePresentationSuccess = await learnCard.invoke.deletePresentation(credentialUriToManage); // Assuming a presentation URI
    // console.log('Deleted Presentation:', deletePresentationSuccess);
} catch (error) {
    console.error('Error in credential/presentation exchange:', error);
}
```

### Boost Management <a href="#retrieving-profiles" id="retrieving-profiles"></a>

Examples covering the lifecycle of Boosts, including creation, retrieval, hierarchy, recipients, permissions, and sending.

#### **Create a Boost**

To create a boost, use the `createBoost` method. This method accepts a `credential` object (which can be an `UnsignedVC` or `VC`) and an optional `metadata` object.

```javascript
const credential = your_credential;
const metadata = {
    name: 'Your Boost Name',
    description: 'Your Boost Description',
};

await learnCard.invoke.createBoost(credential, metadata);
```

#### **Get a Boost**

To get a boost, use the `getBoost` method. This method accepts a `uri` parameter.

```javascript
const uri = 'your_boost_uri';
await learnCard.invoke.getBoost(uri);
```

#### **Send a Boost**

To send a boost to another profile, use the `sendBoost` method. This method accepts a `profileId`, a `boostUri` parameter, and an optional `encrypt` parameteropconst profileId = 'janesmith';

```javascript
const boostUri = 'your_boost_uri';
const encrypt = true;

learnCard.invoke.sendBoost(profileId, boostUri, encrypt);
```

These are the API calls related to boosts management in the LearnCard Network API. Use these methods to create, update, retrieve, and delete boosts, as well as send boosts to other profiles.

#### Advanced Examples:

```typescript
// Assume vcForBoost is a Verifiable Credential object for the boost content
const vcForBoost = await learnCard.invoke.issueCredential(learnCard.invoke.getTestVc());
const boostMetadataMock = { name: 'My First Boost', category: 'Achievement' };
const parentBoostUri = 'uri:boost:parent123'; // Example
const childBoostUri = 'uri:boost:child456'; // Example
const someBoostUri = 'uri:boost:abc789'; // Example
const profileIdForBoost = 'carol.manager';
const boostQueryMock = { category: 'Education' };
const paginationOptions = { limit: 5 };

try {
    // --- Creating Boosts ---
    const boostUri = await learnCard.invoke.createBoost(vcForBoost, boostMetadataMock);
    console.log('Created Boost URI:', boostUri);

    const newChildBoostUri = await learnCard.invoke.createChildBoost(parentBoostUri, vcForBoost, {
        name: 'Child Boost 1',
    });
    console.log('Created Child Boost URI:', newChildBoostUri);

    // --- Retrieving Boosts ---
    const boostDetails = await learnCard.invoke.getBoost(boostUri);
    console.log('Boost Details:', boostDetails);

    const allBoosts = await learnCard.invoke.getPaginatedBoosts({
        query: boostQueryMock,
        ...paginationOptions,
    });
    console.log('Paginated Boosts:', allBoosts.records);

    const boostCount = await learnCard.invoke.countBoosts(boostQueryMock);
    console.log('Total Boosts matching query:', boostCount);

    // --- Boost Hierarchy ---
    const children = await learnCard.invoke.getBoostChildren(parentBoostUri, {
        numberOfGenerations: 1,
        ...paginationOptions,
    });
    console.log(`Children of ${parentBoostUri}:`, children.records);

    const parents = await learnCard.invoke.getBoostParents(newChildBoostUri, {
        numberOfGenerations: 1,
        ...paginationOptions,
    });
    console.log(`Parents of ${newChildBoostUri}:`, parents.records);

    const makeParentSuccess = await learnCard.invoke.makeBoostParent({
        parentUri: parentBoostUri,
        childUri: newChildBoostUri,
    });
    console.log('Made parent successful:', makeParentSuccess);

    // const removeParentSuccess = await learnCard.invoke.removeBoostParent({ parentUri: parentBoostUri, childUri: newChildBoostUri });
    // console.log('Removed parent successful:', removeParentSuccess);

    // --- Boost Recipients & Permissions ---
    const recipients = await learnCard.invoke.getPaginatedBoostRecipients(
        someBoostUri,
        10,
        undefined,
        true
    );
    console.log(`Recipients for ${someBoostUri}:`, recipients.records);

    const admins = await learnCard.invoke.getBoostAdmins(someBoostUri, { includeSelf: true });
    console.log(`Admins for ${someBoostUri}:`, admins.records);

    const addAdminSuccess = await learnCard.invoke.addBoostAdmin(someBoostUri, profileIdForBoost);
    console.log(`Added ${profileIdForBoost} as admin to ${someBoostUri}:`, addAdminSuccess);

    const permissions = await learnCard.invoke.getBoostPermissions(someBoostUri, profileIdForBoost);
    console.log(`Permissions for ${profileIdForBoost} on ${someBoostUri}:`, permissions);

    const updatePermsSuccess = await learnCard.invoke.updateBoostPermissions(
        someBoostUri,
        { canEdit: true },
        profileIdForBoost
    );
    console.log('Updated permissions:', updatePermsSuccess);

    // --- Updating & Deleting Boosts ---
    const boostUpdates = { description: 'Updated description for this amazing boost!' };
    const updateBoostSuccess = await learnCard.invoke.updateBoost(someBoostUri, boostUpdates);
    console.log('Boost update success:', updateBoostSuccess);

    // const deleteBoostSuccess = await learnCard.invoke.deleteBoost(someBoostUri);
    // console.log('Boost delete success:', deleteBoostSuccess);

    // --- Sending Boosts ---
    const targetProfileForBoost = 'dave.recipient';
    const sentBoostCredentialUri = await learnCard.invoke.sendBoost(
        targetProfileForBoost,
        someBoostUri,
        { encrypt: true }
    );
    console.log(
        `Sent boost ${someBoostUri} to ${targetProfileForBoost}. Credential URI:`,
        sentBoostCredentialUri
    );
} catch (error) {
    console.error('Error in boost management:', error);
}
```

### Signing Authorities & Claim Links <a href="#retrieving-profiles" id="retrieving-profiles"></a>

Examples for registering and using signing authorities, and generating/claiming boosts via links.

```typescript
const authorityEndpoint = 'https://my-authority.example.com/sign';
const authorityName = 'MyOrg Signer';
const authorityDid = 'did:example:authority123'; // DID of the signing authority
const boostUriForClaimLink = 'uri:boost:claimable123';

// LCNBoostClaimLinkSigningAuthorityType
const claimLinkSigningAuthorityConfig = {
    endpoint: authorityEndpoint,
    name: authorityName,
    did: authorityDid,
};

// LCNBoostClaimLinkOptionsType
const claimLinkOptions = { ttlSeconds: 86400, totalUses: 10 }; // e.g., expires in 24 hours, 10 max claims

try {
    // --- Signing Authorities ---
    const registerSuccess = await learnCard.invoke.registerSigningAuthority(
        authorityEndpoint,
        authorityName,
        authorityDid
    );
    console.log('Registered Signing Authority:', registerSuccess);

    const authorities = await learnCard.invoke.getRegisteredSigningAuthorities();
    console.log('Retrieved Signing Authorities:', authorities);

    const singleAuthority = await learnCard.invoke.getRegisteredSigningAuthority(
        authorityEndpoint,
        authorityName
    );
    console.log('Single Retrieved Authority:', singleAuthority);

    // --- Claim Links ---
    const claimLinkData = await learnCard.invoke.generateClaimLink(
        boostUriForClaimLink,
        claimLinkSigningAuthorityConfig,
        claimLinkOptions
    );
    console.log('Generated Claim Link Data:', claimLinkData);
    // (Off-band: share this link/data with a user)

    // User claims the boost using the link data
    // This would typically be called by a different learnCard instance/user
    // const claimedCredentialUri = await otherLearnCard.invoke.claimBoostWithLink(claimLinkData.boostUri, claimLinkData.challenge);
    // console.log('Boost claimed via link, Credential URI:', claimedCredentialUri);
} catch (error) {
    console.error('Error with Signing Authorities or Claim Links:', error);
}
```

### ConsentFlow Contracts <a href="#retrieving-profiles" id="retrieving-profiles"></a>

Examples for creating and managing Consent Flow Contracts, user consent, and data access.

```typescript
// Assume consentFlowContractDefinition, consentTermsObjectMock are defined according to your types
const consentFlowContractDefinition = {
    contract: { read: { personal: { name: { required: true } } }, write: {} },
    name: 'Data Sharing Agreement',
    description: 'Share basic profile info.',
};
const consentTermsObjectMock = { read: { personal: { name: 'Consented Name' } }, write: {} }; // User's specific terms based on contract

const contractUriToManage = 'uri:contract:xyz789';
const termsUriToManage = 'uri:terms:abc123'; // URI of a specific consent instance
const userDidForConsentData = 'did:example:user123';
const queryOptions = { limit: 10 };

try {
    // --- Contract Management (by Owner) ---
    const newContractUri = await learnCard.invoke.createContract(consentFlowContractDefinition);
    console.log('Created Contract URI:', newContractUri);

    // const autoBoostConfigs = [{ boostUri: 'uri:boost:auto123', signingAuthority: { endpoint: '...', name: '...' } }];
    // const addAutoBoostSuccess = await learnCard.invoke.addAutoBoostsToContract(newContractUri, autoBoostConfigs);
    // console.log('Added Auto-Boosts:', addAutoBoostSuccess);

    const contractDetails = await learnCard.invoke.getContract(newContractUri);
    console.log('Contract Details:', contractDetails);

    const allMyContracts = await learnCard.invoke.getContracts(queryOptions);
    console.log('My Contracts:', allMyContracts.records);

    // const deleteContractSuccess = await learnCard.invoke.deleteContract(newContractUri);
    // console.log('Deleted Contract:', deleteContractSuccess);

    // --- User Consent Actions ---
    // (User consents to a contract)
    const consentTermsDetails = {
        terms: consentTermsObjectMock,
        expiresAt: new Date(Date.now() + 3600 * 1000 * 24 * 30).toISOString(),
    }; // Expires in 30 days
    const consentedTermsUri = await learnCard.invoke.consentToContract(
        contractUriToManage,
        consentTermsDetails
    );
    console.log('Consented to Contract, Terms URI:', consentedTermsUri);

    // (User retrieves their consented contracts)
    const myConsentedContracts = await learnCard.invoke.getConsentedContracts(queryOptions);
    console.log('My Consented Contracts (Terms):', myConsentedContracts.records);

    // (User updates their terms for a specific consent)
    const updatedTermsDetails = {
        terms: { ...consentTermsObjectMock, read: { personal: { name: 'Updated Name' } } },
        oneTime: true,
    };
    const updateTermsSuccess = await learnCard.invoke.updateContractTerms(
        termsUriToManage,
        updatedTermsDetails
    );
    console.log('Updated Contract Terms:', updateTermsSuccess);

    // (User withdraws consent)
    // const withdrawSuccess = await learnCard.invoke.withdrawConsent(termsUriToManage);
    // console.log('Withdrew Consent:', withdrawSuccess);

    // --- Data Access & Transactions (by Contract Owner or authorized profiles) ---
    const consentDataForContract = await learnCard.invoke.getConsentFlowData(
        contractUriToManage,
        queryOptions
    );
    console.log('Consented Data for Contract:', consentDataForContract.records);

    const consentDataForDid = await learnCard.invoke.getConsentFlowDataForDid(
        userDidForConsentData,
        queryOptions
    );
    console.log(
        `Consented Data involving DID ${userDidForConsentData}:`,
        consentDataForDid.records
    );

    // const allConsentData = await learnCard.invoke.getAllConsentFlowData({}, queryOptions);
    // console.log('All Consented Data for my contracts:', allConsentData.records);

    const transactions = await learnCard.invoke.getConsentFlowTransactions(
        termsUriToManage,
        queryOptions
    );
    console.log('Consent Transactions for Terms:', transactions.records);

    const credentialsForContractTerms = await learnCard.invoke.getCredentialsForContract(
        termsUriToManage,
        queryOptions
    );
    console.log('Credentials related to Contract Terms:', credentialsForContractTerms.records);

    // --- Writing & Syncing Credentials based on Consent ---
    // (Owner writes a credential to a consented user for a specific boost related to the contract)
    const didOfConsentedUser = 'did:example:consenter123';
    const boostUriRelatedToContract = 'uri:boost:contractRelatedBoost456';
    // const writtenCredentialUri = await learnCard.invoke.writeCredentialToContract(didOfConsentedUser, contractUriToManage, vcMock, boostUriRelatedToContract);
    // console.log('Credential written to contract for user:', writtenCredentialUri);

    // (Consenter syncs their credentials to the contract terms)
    const credentialsToSyncByCategory = {
        'Achievement': ['uri:credential:ach1', 'uri:credential:ach2'],
    };
    const syncSuccess = await learnCard.invoke.syncCredentialsToContract(
        termsUriToManage,
        credentialsToSyncByCategory
    );
    console.log('Synced credentials to contract:', syncSuccess);

    // --- Verifying Consent ---
    const isConsentValid = await learnCard.invoke.verifyConsent(
        termsUriToManage,
        userDidForConsentData
    ); // Check if userDidForConsentData has valid consent for termsUriToManage
    console.log(
        `Consent valid for ${userDidForConsentData} on ${termsUriToManage}:`,
        isConsentValid
    );
} catch (error) {
    console.error('Error in Consent Flow management:', error);
}
```

### DID Metadata Management <a href="#retrieving-profiles" id="retrieving-profiles"></a>

Examples for adding, retrieving, updating, and deleting DID metadata.

```typescript
// Assume didDocumentPartial is an object with some DID Document properties
const didDocumentPartial = {
    service: [
        { id: '#service-1', type: 'MyService', serviceEndpoint: 'https://example.com/service' },
    ],
};
const metadataIdToManage = 'some-metadata-id'; // This would be an ID returned by addDidMetadata or associated with a DID

try {
    const addMetadataSuccess = await learnCard.invoke.addDidMetadata(didDocumentPartial);
    console.log('Added DID Metadata Success (or ID):', addMetadataSuccess); // Might return ID or boolean

    const myDidMetadataList = await learnCard.invoke.getMyDidMetadata();
    console.log('My DID Metadata Records:', myDidMetadataList);

    if (myDidMetadataList.length > 0) {
        const firstMetadataId = myDidMetadataList[0].id;
        const specificMetadata = await learnCard.invoke.getDidMetadata(firstMetadataId);
        console.log(`Specific DID Metadata for ${firstMetadataId}:`, specificMetadata);

        const updatesToMetadata = {
            service: [
                {
                    id: '#service-1',
                    type: 'UpdatedService',
                    serviceEndpoint: 'https://new.example.com/service',
                },
            ],
        };
        const updateMetadataSuccess = await learnCard.invoke.updateDidMetadata(
            firstMetadataId,
            updatesToMetadata
        );
        console.log('Updated DID Metadata:', updateMetadataSuccess);

        // const deleteMetadataSuccess = await learnCard.invoke.deleteDidMetadata(firstMetadataId);
        // console.log('Deleted DID Metadata:', deleteMetadataSuccess);
    }
} catch (error) {
    console.error('Error managing DID Metadata:', error);
}
```

### Claim Hooks <a href="#retrieving-profiles" id="retrieving-profiles"></a>

Examples for managing claim hooks for Boosts.

```typescript
// --- Claim Hooks ---

// Example 1: Creating a 'GRANT_PERMISSIONS' Claim Hook
// This hook would grant specific permissions on 'targetBoostUri'
// when 'sourceBoostClaimUri' is successfully claimed.
const grantPermissionsHookDefinition: ClaimHook = {
    type: 'GRANT_PERMISSIONS', // As per ClaimHookTypeValidator
    data: {
        claimUri: 'uri:boost:sourceBoostClaimUri123', // The boost that, when claimed, triggers this hook
        targetUri: 'uri:boost:targetBoostUri456', // The boost on which permissions will be granted
        permissions: {
            // Partial<BoostPermissions> from BoostPermissionsValidator
            canEdit: true,
            canIssue: false,
            // Add other permission fields as needed, e.g., canViewAnalytics: true
        },
    },
};

// Example 2: Creating an 'ADD_ADMIN' Claim Hook
// This hook would make the claimer an admin of 'targetResourceUriForAdmin'
// when 'anotherSourceBoostUri' is successfully claimed.
const addAdminHookDefinition: ClaimHook = {
    type: 'ADD_ADMIN', // As per ClaimHookTypeValidator
    data: {
        claimUri: 'uri:boost:anotherSourceBoostUri789', // The boost that, when claimed, triggers this hook
        targetUri: 'uri:boost:targetResourceUriForAdmin000', // The resource (e.g., another boost) the claimer becomes admin of
    },
};

const boostUriToQueryHooksFor = 'uri:boost:sourceBoostClaimUri123';
const paginationOptions = { limit: 10 };

// Example Query for getClaimHooksForBoost
const specificClaimHookQuery: ClaimHookQuery = {
    type: 'GRANT_PERMISSIONS', // Filter for hooks of type 'GRANT_PERMISSIONS'
    data: {
        targetUri: 'uri:boost:targetBoostUri456', // Further filter by the targetUri in the hook's data
        // You could also add filters for permissions if your BoostPermissionsQueryValidator supports it
        // permissions: { canEdit: true }
    },
};

try {
    // Create the 'GRANT_PERMISSIONS' hook
    const grantPermissionsHookId = await learnCard.invoke.createClaimHook(
        grantPermissionsHookDefinition
    );
    console.log('Created GRANT_PERMISSIONS Claim Hook ID:', grantPermissionsHookId);

    // Create the 'ADD_ADMIN' hook
    const addAdminHookId = await learnCard.invoke.createClaimHook(addAdminHookDefinition);
    console.log('Created ADD_ADMIN Claim Hook ID:', addAdminHookId);

    // Get all claim hooks for a specific boost URI with pagination
    const allHooksForBoost = await learnCard.invoke.getClaimHooksForBoost({
        uri: boostUriToQueryHooksFor,
        ...paginationOptions,
    });
    console.log(`All Claim Hooks for ${boostUriToQueryHooksFor}:`, allHooksForBoost.records);

    // Get claim hooks for a specific boost URI with a query and pagination
    const filteredHooksForBoost = await learnCard.invoke.getClaimHooksForBoost({
        uri: boostUriToQueryHooksFor,
        query: specificClaimHookQuery,
        ...paginationOptions,
    });
    console.log(
        `Filtered Claim Hooks for ${boostUriToQueryHooksFor}:`,
        filteredHooksForBoost.records
    );

    // Delete a claim hook (e.g., the first one we created)
    // const deleteGrantPermissionsHookSuccess = await learnCard.invoke.deleteClaimHook(grantPermissionsHookId);
    // console.log('Deleted GRANT_PERMISSIONS Claim Hook:', deleteGrantPermissionsHookSuccess);
} catch (error) {
    console.error('Error managing Claim Hooks:', error);
}
```

### Authorization Grants & API Tokens <a href="#retrieving-profiles" id="retrieving-profiles"></a>

Examples for managing authorization grants and generating API tokens.

{% hint style="info" %}
Click here for more information on [Auth Grants and Scopes](/core-concepts/architecture-and-principles/auth-grants-and-api-tokens).
{% endhint %}

```typescript
const authGrantPartial = {
    name: 'External Service XYZ',
    description: 'Auth Grant for XYZ Service',
    scope: 'read:profile write:boosts',
    expiresAt: new Date(Date.now() + 3600000).toISOString(),
};
const authGrantIdToManage = 'grant-id-xyz'; // Example ID of an existing grant

try {
    const newAuthGrantId = await learnCard.invoke.addAuthGrant(authGrantPartial);
    console.log('Added Auth Grant ID:', newAuthGrantId);

    const specificAuthGrant = await learnCard.invoke.getAuthGrant(newAuthGrantId);
    console.log('Specific Auth Grant:', specificAuthGrant);

    const allAuthGrants = await learnCard.invoke.getAuthGrants({ limit: 10 });
    console.log('All Auth Grants:', allAuthGrants);

    const updatesToAuthGrant = { description: 'Updated grant for special access' };
    const updateGrantSuccess = await learnCard.invoke.updateAuthGrant(
        newAuthGrantId,
        updatesToAuthGrant
    );
    console.log('Updated Auth Grant:', updateGrantSuccess);

    const apiToken = await learnCard.invoke.getAPITokenForAuthGrant(newAuthGrantId);
    console.log('API Token for Auth Grant:', apiToken);

    // const revokeGrantSuccess = await learnCard.invoke.revokeAuthGrant(newAuthGrantId);
    // console.log('Revoked Auth Grant:', revokeGrantSuccess);

    // const deleteGrantSuccess = await learnCard.invoke.deleteAuthGrant(newAuthGrantId);
    // console.log('Deleted Auth Grant:', deleteGrantSuccess);
} catch (error) {
    console.error('Error managing Auth Grants:', error);
}
```

#### Create an AuthGrant

Use the LearnCard SDK's `addAuthGrant` method to create a new AuthGrant:

```javascript
const authGrantID = await learnCard.invoke.addAuthGrant({
    name: 'Example Auth Grant',
    description: 'Full Access Auth Grant',
    scope: '*:*',
});
```

#### AuthGrant Properties

* `id`: Unique identifier (auto-generated if not provided)
* `name`: Name of the AuthGrant
* `description`: (Optional) Description of the purpose or use case
* `challenge`: Security challenge string (must start with AuthGrant prefix)
* `status`: Either 'active' or 'revoked'
* `scope`: Permission scope string
* `createdAt`: ISO 8601 datetime string of creation (auto-generated if not provided)
* `expiresAt`: (Optional) ISO 8601 datetime string for expiration

#### Generating an API Token

Once you have an AuthGrant, you can generate an API token using the `getAPITokenForAuthGrant` method:

```javascript
const apiToken = await learnCard.invoke.getAPITokenForAuthGrant(authGrantID);
```

This token encapsulates the permissions defined in the AuthGrant and should be used for authentication in API requests.

#### Using the API Token for HTTP Requests

Use the generated API token in the Authorization header with the Bearer scheme:

```javascript
const response = await fetch(
    'https://api.learncard.network/api/boost/send/via-signing-authority/RECIPIENT_ID',
    {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${apiToken}`,
        },
        body: JSON.stringify(payload),
    }
);
```

#### Generate API Token End-to-End Example

Here's a complete example showing how to:

1. Create an AuthGrant
2. Generate an API token
3. Use the token to send a boost via the HTTP API

```javascript
// Step 1: Create an AuthGrant with specific permissions
const grantId = await learnCard.invoke.addAuthGrant({
    name: 'Boost Sender Auth',
    description: 'Permission to send boosts',
    scope: 'boosts:write',
});

// Step 2: Generate an API token from the AuthGrant
const token = await learnCard.invoke.getAPITokenForAuthGrant(grantId);

// Step 3: Prepare the payload for your API request
const payload = {
    boostUri: 'uri-of-the-boost-to-send',
    signingAuthority: 'your-signing-authority',
};

// Step 4: Make an authenticated HTTP request using the token
const response = await fetch(
    `https://api.learncard.network/api/boost/send/via-signing-authority/RECIPIENT_PROFILE_ID`,
    {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${token}`,
        },
        body: JSON.stringify(payload),
    }
);

// Step 5: Process the response
if (response.status === 200) {
    const sentBoostUri = await response.json();
    console.log(`Boost sent successfully: ${sentBoostUri}`);
} else {
    console.error(`Error sending boost: ${response.status}`);
    const errorDetails = await response.json();
    console.error(errorDetails);
}
```

#### Retrieving AuthGrants

```javascript
// Get a single AuthGrant by ID
const authGrant = await learnCard.invoke.getAuthGrant(authGrantID);

// Get multiple AuthGrants with optional filtering
const authGrants = await learnCard.invoke.getAuthGrants({
    query: {
        status: 'active',
        name: { contains: 'API' },
    },
});
```

#### Updating AuthGrants

```javascript
// Update an existing AuthGrant
const updatedGrant = await learnCard.invoke.updateAuthGrant(authGrantID, {
    description: 'Updated description',
});
```

#### Revoking AuthGrants

```javascript
// Revoke an AuthGrant to invalidate its tokens
await learnCard.invoke.revokeAuthGrant(authGrantID);

// Or delete it completely
await learnCard.invoke.deleteAuthGrant(authGrantID);
```

### General Utilities <a href="#retrieving-profiles" id="retrieving-profiles"></a>

```typescript
const someLearnCardNetworkUri = 'uri:boost:abc123'; // Example URI

try {
    // Resolve any LCN URI to its underlying object (VC, VP, Contract, etc.)
    const resolvedObject = await learnCard.invoke.resolveFromLCN(someLearnCardNetworkUri);
    console.log(`Resolved object for ${someLearnCardNetworkUri}:`, resolvedObject);

    // Get the underlying LCN Client instance (if advanced usage is needed)
    const lcnClient = await learnCard.invoke.getLCNClient();
    console.log('LCN Client instance retrieved.');
    // Now you could potentially use methods directly on lcnClient if necessary,
    // though most operations should be covered by learnCard.invoke wrappers.
} catch (error) {
    console.error('Error with LCN utilities:', error);
}
```


# Plugin API Reference

### [Plugins](#plugin-framework)

The plugin system allows for extending the functionality of LearnCard Core. Plugins can implement control planes and add custom methods to a LearnCard instance.

Plugins are defined using the `Plugin` type, which takes three type parameters:

1. **Name**: A string literal representing the plugin name
2. **Planes**: The control planes the plugin implements
3. **Methods**: Custom methods the plugin provides

For example, a plugin implementing the Read and Store planes would be typed like:

```typescript
Plugin<'MyPlugin', 'read' | 'store', {}>
```

A plugin providing a custom method would be typed like:

```typescript
Plugin<'MyPlugin', any, { customMethod: (param: string) => Promise<string> }>
```

Plugins can be added to a LearnCard instance using the `addPlugin` method:

```typescript
const enhancedLearnCard = await learnCard.addPlugin(myPlugin);
```

### Providers

Plugins are provided by community and core contributors. Given the plugin’s nature, they may also host and maintain infrastructure necessary to sustain its call patterns. For example, an IPFS storage provider might also provide a pinning service, a DID document provider might host document endpoints, etc.

These details are to be provided along with the plugin.

## How to Create a Plugin

If you're looking for a guide on creating a plugin, check-out the [Build a Plugin ](/how-to-guides/deploy-infrastructure/the-simplest-plugin)guide:

{% content-ref url="/pages/82rABq5FwUWDUOHlrQj2" %}
[Build a Plugin](/how-to-guides/deploy-infrastructure/the-simplest-plugin)
{% endcontent-ref %}

## The Plugin Type

If you're creating a plugin, it is *highly* recommended you use TypeScript and take advantage of the `Plugin` type.

### How to use the `Plugin` Type

#### Complex Example Plugin

The `Plugin` type is a [generic](https://www.typescriptlang.org/docs/handbook/2/generics.html) type, taking in up to five total generic parameters. A Plugin using all five parameters would look like this:

{% code title="Complex Plugin" %}

```typescript
import { Plugin } from '@learncard/core';

type Methods = { foo: () => 'bar' }';
type DependentMethods = { bar: () => 'baz' };

type ComplexPlugin = Plugin<'Complex', 'store', Methods, 'id', DependentMethods>;
```

{% endcode %}

This type describes a plugin named `'Complex'` that implements the [Store](/core-concepts/architecture-and-principles/control-planes#store-control-plane) [Control Plane](/core-concepts/architecture-and-principles/control-planes), as well as the method `foo`. This plugin is also [dependent](#depending-on-plugins) on plugins that implement the [ID](/core-concepts/architecture-and-principles/control-planes#id-control-plane) Control Plane, as well as the method *bar*.

Let's break down what each of these arguments are doing one at a time.

#### Arg 1: Name

```typescript
//                          VVVVVVVVV
type ComplexPlugin = Plugin<'Complex', 'store', Methods, 'id', DependentMethods>;
//                          ^^^^^^^^^
```

This argument simply names a plugin. It is a required string, and is used by some Control Planes.

Specifying a name will force objects that have been set to this type to use that name!

#### Arg 2: Planes Implemented

```typescript
//                                     VVVVVVV
type ComplexPlugin = Plugin<'Complex', 'store', Methods, 'id', DependentMethods>;
//                                     ^^^^^^^
```

This argument specifies which [Control Planes](/core-concepts/architecture-and-principles/control-planes) the plugin implements. It can be:

* `any` or `never`, which specifies that this plugin does not implement any Control Planes
* A single string (such as used in the example above), which specifies that this plugin implements a single Control Plane
* A [union](https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#union-types) of strings (e.g. `'store' | 'read'`), which specifies that this plugin implements multiple Control Planes

This argument defaults to `any`, specifying that this plugin does not implement any Control Planes.

Specifying one or more Control Planes implemented will force objects that have been set to this type to implement those planes!

#### Arg 3: Methods Implemented

```typescript
//                                              VVVVVVV
type ComplexPlugin = Plugin<'Complex', 'store', Methods, 'id', DependentMethods>;
//                                              ^^^^^^^
```

This argument specifies which [methods](#implementing-methods) the plugin implements. It is an object whose values are functions.

This argument defaults to `Record<never, never>`, specifying that this plugin does not implement any methods.

Specifying methods implemented will force objects that have been set to this type to implement those methods!

#### Arg 4: Dependent Planes

```typescript
//                                                       VVVV
type ComplexPlugin = Plugin<'Complex', 'store', Methods, 'id', DependentMethods>;
//                                                       ^^^^
```

This argument specifies which [Control Planes](/core-concepts/architecture-and-principles/control-planes) the plugin depends on. It can be:

* `any` or `never`, which specifies that this plugin does not depend on any Control Planes
* A single string (such as used in the example above), which specifies that this plugin depends on a single Control Plane
* A [union](https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#union-types) of strings (e.g. `'store' | 'read'`), which specifies that this plugin depends on multiple Control Planes

This argument defaults to `never`, specifying that this plugin does not depend on any Control Planes.

Specifying one or more Dependent Control Planes will add those planes to the [implicit LearnCard](#the-implicit-learncard-object) passed into each plugin method!

{% hint style="warning" %}
Specifying Dependent Planes here will *not* force LearnCards to implement those planes when adding this plugin! Please see [Depending on Plugins](#depending-on-plugins) for more information.
{% endhint %}

#### Arg 5: Dependent Methods

```typescript
//                                                             VVVVVVVVVVVVVVVV
type ComplexPlugin = Plugin<'Complex', 'store', Methods, 'id', DependentMethods>;
//                                                             ^^^^^^^^^^^^^^^^
```

This argument specifies which [methods](#depending-on-methods) the plugin depends on. It is an object whose values are functions.

This argument defaults to `Record<never, never>`, specifying that this plugin does not depend on any methods.

Specifying dependent methods will add those methods to the [implicit LearnCard](#the-implicit-learncard-object) passed into each plugin method!

{% hint style="warning" %}
Specifying Dependent Methods here will *not* force `LearnCards` to implement those methods when adding this plugin! Please see [Depending on Plugins](#depending-on-plugins) for more information.
{% endhint %}

## The LearnCard Type

To add better type safety to a project using LearnCards, it is *highly* recommended you use TypeScript and take advantage of the `LearnCard` type.

### How to use the `LearnCard` type

#### Option 1: Specify a list of plugins

If you know the exact order and number of plugins you have, you can use the `LearnCard` type to specify a `LearnCard` with those plugins like so:

```typescript
import { LearnCard } from '@learncard/core';
import { PluginA, PluginB, PluginC } from './plugins';

type CustomLearnCard = LearnCard<[PluginA, PluginB, PluginC]>;
```

With this code, `CustomLearnCard` will automatically infer all methods and planes implemented by `PluginA`, `PluginB`, and `PluginC`!

#### Option 2: Specify implemented planes and/or methods

If you don't know the exact order and number of plugins you have, but you know that you would like to specify a `LearnCard` that implements certain planes or methods, you can do that like this:

```typescript
import { LearnCard } from '@learncard/core';

type ImplementsIdPlane = LearnCard<any, 'id'>;
type ImplementsFoo = LearnCard<any, any, { foo: () => 'bar'; }>;
type ImplementsBoth = LearnCard<any, 'id', { foo: () => 'bar': }>;
```

With this code, `ImplementsIdPlane` will accept any `LearnCard` that has plugins that implement the [ID](/core-concepts/architecture-and-principles/control-planes#id-control-plane) [Control Plane](/core-concepts/architecture-and-principles/control-planes), `ImplementsFoo` will accept any `LearnCard` that implements a method named `foo` that returns `'bar'`, and `ImplementsBoth` will accept any `LearnCard` that implements both.

## Implementing Control Planes

In order to promote convergence across Plugin APIs to support common functionality over complex workflows, plugins may choose to implement [Control Planes](/core-concepts/architecture-and-principles/control-planes). Because each plane is slightly different, *how* they are actually implemented will also be slightly different. For the most part, there are some standard conventions that plugins must follow when implementing methods for a plane.

To demonstrate this, let's build a plugin that implements the [Read](/core-concepts/architecture-and-principles/control-planes#read-control-plane) and [Store](/core-concepts/architecture-and-principles/control-planes#store-control-plane) planes using [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).

### Types

Let's start with the types! We will use [the `Plugin` type](#the-plugin-type) to define a Plugin that implements the Read and Store planes like so:

{% code title="src/types.ts" lineNumbers="true" %}

```typescript
import { Plugin } from '@learncard/core';

export type LocalStoragePlugin = Plugin<'LocalStorage', 'read' | 'store'>;
```

{% endcode %}

### Implementation

#### Skeleton

Before actually implementing the localStorage functionality, let's build out the skeleton of what our implementation will look like:

<pre class="language-typescript" data-title="src/index.ts" data-line-numbers><code class="lang-typescript">import { LocalStoragePlugin } from './types';

export const getLocalStoragePlugin = (): LocalStoragePlugin => {
    return {
        name: 'LocalStorage',
<strong>        read: { get: () => {} },
</strong><strong>        store: { upload: () => {} },
</strong>        methods: {},
    };
};
</code></pre>

Because we specified that this plugin is implementing the `read` and `store` planes, we *must* include those keys in the returned `Plugin` object. The Read Plane requires we implement a `get` method, and the Store Plane requires we implement at least an `upload` method, so these methods have been stubbed out.

### Implementing the Store Plane

#### URI Scheme

Let's start by implementing the Store Plane! The [Store Plane docs](/core-concepts/architecture-and-principles/control-planes#store-control-plane) mention that the `upload` method should return a [`URI`](/core-concepts/credentials-and-data/uris), so we will now devise a URI scheme. It looks like this:

```typescript
`lc:localStorage:${id}`
```

Where `id` is the identifier used as a key in `localStorage`.

#### UUID

To easily create unique identifiers, we will use the [`uuid`](https://www.npmjs.com/package/uuid) npm package. Creating an ID using that package looks like this:

```typescript
import { v4 as uuidv4 } from 'uuid';
uuidv4(); // '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'
```

#### Putting it all together

With our URI scheme defined and ID generation in place, let's implement our Store Plane!

<pre class="language-typescript" data-title="src/index.ts" data-line-numbers><code class="lang-typescript">import { v4 as uuidv4 } from 'uuid';

import { LocalStoragePlugin } from './types';

export const getLocalStoragePlugin = (): LocalStoragePlugin => {
    return {
        name: 'LocalStorage',
        read: { get: () => {} },
<strong>        store: { 
</strong><strong>            upload: async (_learnCard, vc) => {
</strong><strong>                const id = uuidv4();
</strong><strong>                
</strong><strong>                localStorage.setItem(id, JSON.stringify(vc));
</strong><strong>                
</strong><strong>                return `lc:localStorage:${id}`;
</strong><strong>            } 
</strong><strong>        },
</strong>        methods: {},
    };
};
</code></pre>

{% hint style="info" %}
Wondering what that unused `_learnCard` variable is about? It's [the implicit LearnCard](#the-implicit-learncard-object)! Check out what it is and how to use it [here](#the-implicit-learncard-object)!
{% endhint %}

With this code in place, the Store Plane has successfully been implemented! Verifiable Credentials can now be stored by a LearnCard using this plugin with the following code:

```typescript
const uri = await learnCard.store.LocalStorage.upload(vc);
```

Now the we can *store* credentials in localStorage, let's implement *getting* them out!

### Implementing the Read Plane

To implement the Read Plane, we simply need to verify that the incoming `URI` is one that matches our scheme, and if so, read the value stored in localStorage! This can be done with the following code:

<pre class="language-typescript" data-title="src/index.ts" data-line-numbers><code class="lang-typescript">import { v4 as uuidv4 } from 'uuid';

import { LocalStoragePlugin } from './types';

export const getLocalStoragePlugin = (): LocalStoragePlugin => {
    return {
        name: 'LocalStorage',
<strong>        read: { 
</strong><strong>            get: async (_learnCard, uri) => {
</strong><strong>                const sections = uri.split(':');
</strong><strong>                
</strong><strong>                if (sections.length !== 3 || !uri.startsWith('lc:localStorage')) {
</strong><strong>                    return undefined; // Let another plugin resolve this URI!
</strong><strong>                }
</strong><strong>                
</strong><strong>                const storedValue = localStorage.getItem(sections[2]);
</strong><strong>                
</strong><strong>                return storedValue ? JSON.parse(storedValue) : undefined;
</strong><strong>            } 
</strong><strong>        },
</strong>        store: { 
            upload: async (_learnCard, vc) => {
                const id = uuidv4();
                
                localStorage.setItem(id, JSON.stringify(vc));
                
                return `lc:localStorage:${id}`;
            } 
        },
        methods: {},
    };
};
</code></pre>

With this in place, the Read and Store planes have been implemented and LearnCards may use our plugin to store and retrieve credentials from localStorage with the following code:

```typescript
const uri = await learnCard.store.LocalStorage.upload(vc);

// ...Later

const vc = await learnCard.read.get(uri);
```

## Implementing Methods

Sometimes plugins need to expose some bespoke logic that doesn't fit neatly into one of the [Control Planes](/core-concepts/architecture-and-principles/control-planes). Plugin methods allow plugins to expose this logic directly on the resulting LearnCard object.

We have already seen this in action in [The Simplest Plugin](/how-to-guides/deploy-infrastructure/the-simplest-plugin), but let's go into a bit more depth about what's happening here by making a quick plugin that implements a basic counter.

## Types

Before implementing methods on a Plugin object, it's best to get the types in order. In general, starting with the types can be easier to think through, and once they're in place, they can guide the implementation. To add types for methods, we use the third generic argument of [the `Plugin` type](#the-plugin-type).

{% code title="src/types.ts" lineNumbers="true" %}

```typescript
import { Plugin } from '@learncard/core';

export type CounterPluginMethods = {
    getCounterValue: () => number;
    incrementCounter: () => void;
    resetCounter: () => void;
};

export type CounterPlugin = Plugin<'Counter', any, CounterPluginMethods>;
```

{% endcode %}

The types above have defined a Plugin with three methods: `get`, `increment`, and `reset`, which will provide basic counter controls.

## Implementation

### Skeleton

With the above types in place, we can build out a skeleton plugin before actually implementing anything:

{% code title="src/index.ts" lineNumbers="true" %}

```typescript
import { CounterPlugin } from './types';

export const getCounterPlugin = (): CounterPlugin => {
    return {
        name: 'Counter',
        methods: {
            getCounterValue: () => {},
            incrementCounter: () => {},
            resetCounter: () => {},
        },
    };
};
```

{% endcode %}

{% hint style="info" %}
We need to wrap the plugin in a `getCounterPlugin` function to be able to store the counter state later
{% endhint %}

### Implementing the Methods

With our boilerplate out of the way, implementing the Counter plugin will be a cakewalk! 🍰

We will use the lexical scope of the `getCounterPlugin` function to store out counter state, and manipulate it via the exposed methods.

<pre class="language-typescript" data-title="src/index.ts" data-line-numbers><code class="lang-typescript">import { CounterPlugin } from './types';

export const getCounterPlugin = (): CounterPlugin => {
<strong>    let value = 0;
</strong><strong>    
</strong>    return {
        name: 'Counter',
        methods: {
<strong>            getCounterValue: () => value,
</strong><strong>            incrementCounter: () => {
</strong><strong>                value += 1;
</strong><strong>            },
</strong><strong>            resetCounter: () => {
</strong><strong>                value = 0;
</strong><strong>            },
</strong>        },
    };
};
</code></pre>

Our plugin is now complete, and we have successfully implemented bespoke method. Easy as 🍰.

Our plugin can be added to and used in a LearnCard like so:

```typescript
const counterLearnCard = await learnCard.addPlugin(getCounterPlugin());

counterLearnCard.invoke.getCounterValue(); // 0

counterLearnCard.invoke.incrementCounter();
counterLearnCard.invoke.getCounterValue(); // 1

counterLearnCard.invoke.incrementCounter();
counterLearnCard.invoke.incrementCounter();
counterLearnCard.invoke.getCounterValue(); // 3

counterLearnCard.invoke.resetCounter();
counterLearnCard.invoke.getCounterValue(); // 0
```

## The Implicit LearnCard Object

### What is it?

When implementing [Control Planes](/core-concepts/architecture-and-principles/control-planes) or [Methods](#implementing-methods), you may have noticed [an unused `_learnCard` parameter get added.](#the-implicit-learncard-object) This is what we call the *Implicit LearnCard*, and it can be very helpful!

### What does it do?

The Implicit LearnCard allows your Plugin's methods to access an up-to-date version of the LearnCard that it has been added to. This means that you can have access to a full LearnCard without having to wrap your Plugin in a function!

### Why would you use it?

There are a few use-cases for using the Implicit LearnCard, such as:

* Calling a method that is implemented in the same plugin
* Ensuring the most up-to-date method is called

Let's implement a quick plugin that generates names to demonstrate this. The plugin will expose three methods: `generateFirstName`, `generateLastName`, and `generateFullName`. The types for this plugin look like this (using [the `Plugin` type](#the-plugin-type)):

{% code title="src/types.ts" lineNumbers="true" %}

```typescript
import { Plugin } from '@learncard/core';

export type NamePluginMethods = {
    generateFirstName: () => string;
    generateLastName: () => string;
    generateFullName: () => string;
};

export type NamePluginType = Plugin<'Name', any, NamePluginMethods>;
```

{% endcode %}

The implementation for this plugin can have `generateFullName` easily call `generateFirstName` and `generateLastName` without having to define them outside of the function thanks to the Implicit LearnCard:

<pre class="language-typescript" data-title="src/index.ts" data-line-numbers><code class="lang-typescript">import { NamePluginType } from './types';

export const NamePlugin: NamePluginType = {
    name: 'Name',
    methods: {
        generateFirstName: () => 'First', // Not very useful....
        generateLastName: () => 'Last',   // Imagine a huge list of names being chosen at random
<strong>        generateFullName: learnCard => 
</strong><strong>            `${learnCard.invoke.generateFirstName()} ${learnCard.invoke.generateLastName()}`
</strong>    }
};
</code></pre>

While this example may be a bit contrived, it *does* demonstrate a few important benefits of the Implicit LearnCard:

* We were able to reuse plugin methods without defining them outside the plugin
* Other plugins are now able to override the functionality of `generateFirstName` and `generateLastName` and `generateFullName` will *automatically* call the overriden methods!
  * This allows plugins to easily define interfaces for *sub-plugins* or plugin extensions.
  * This also gives plugins the ability to monkey-patch pieces of another plugin, enhancing or changing that earlier plugin's functionality

### When would you not use it?

#### Privacy

The Implicit LearnCard can be *very* handy for plugin extensibility and composition. However, there are times you *don't* want a plugin to be able to be monkey-patched or extended. In such a case, it is a better idea *not* to use the Implicit LearnCard, and just define the re-usable functions outside the plugin:

<pre class="language-typescript" data-title="src/index.ts" data-line-numbers><code class="lang-typescript">import { NamePluginType } from './types';

export const getNamePlugin = (): NamePluginType => {
<strong>    const generateFirstName = () => 'First';
</strong><strong>    const generateLastName = () => 'Last';
</strong>    
    return {
        name: 'Name',
        methods: {
            generateFirstName, // Not very useful....
            generateLastName,   // Imagine a huge list of names being chosen at random
<strong>            generateFullName: learnCard => 
</strong><strong>                `${generateFirstName()} ${generateLastName()}`
</strong>        }
    };
};
</code></pre>

#### Plugin Extensions

Another reason not to use the Implicit LearnCard is when you *specifically* want an old version of a method you are overriding. To demonstrate this, let's build a quick [Verification Extension](https://github.com/learningeconomy/LearnCard/tree/main/docs/sdks/official-plugins/vc/README.md#verification-extension)

### Types

Building a Verification Extension is super easy with the `VerifyExtension` type coming from the [VC Plugin](https://github.com/learningeconomy/LearnCard/tree/main/docs/sdks/official-plugins/vc/README.md):

{% code title="src/types.ts" lineNumbers="true" %}

```typescript
import { Plugin, VerifyExtension } from '@learncard/core';

export type ExtensionPlugin = Plugin<'Extension', any, VerifyExtension>;
```

{% endcode %}

<pre class="language-typescript" data-title="src/index.ts" data-line-numbers><code class="lang-typescript">import { LearnCard, VerifyExtension } from '@learncard/core';
import { ExtensionPlugin } from './types';

export const getExtensionPlugin = (
    learnCard: LearnCard&#x3C;any, any, VerifyExtension>
): ExtensionPlugin => ({
    name: 'Extension',
    methods: {
        verifyCredential: async (_learnCard, credential) => {
<strong>            const verificationCheck = await learnCard.invoke.verifyCredential(credential);
</strong>            
            verificationCheck.checks.push('Extension! 😄');
            
            return verificationCheck;
        }
    }
})
</code></pre>

{% hint style="info" %}
See [Depending on Plugins](#depending-on-plugins) for more information about how we are depending on a LearnCard with the `verifyCredential` method here.
{% endhint %}

The `VerifyExtension` type defines one method: `verifyCredential` that takes in a Verifiable Credential and returns a `VerificationCheck` object. To add our extension, we depend on a LearnCard that already has the `verifyCredential` function (using the same `VerifyExtension` type!), then call the *old* `verifyCredential` function at the top of our *new* `verifyCredential` function.

This pattern allows any number of plugins to add extra verification logic to the `verifyCredential` function easily!

## Depending On Plugins

While it is useful for [The Simplest Plugin](/how-to-guides/deploy-infrastructure/the-simplest-plugin) to add its own isolated logic to a LearnCard, part of the beauty of LearnCard plugins is to *depend* on other plugins 💪

Plugin dependence comes in two flavors:

* Depending on a [Control Plane](/core-concepts/architecture-and-principles/control-planes)
* Depending on one or more [methods](#depending-on-methods).

### Boilerplate Plugins

To demonstrate this, let's create a simple base plugin, as well as two plugins that we will depend on: one for Control Planes, and one for methods.

#### Base Plugin

{% code title="src/dependence/types.ts" lineNumbers="true" %}

```typescript
import { Plugin } from '@learncard/core';

export type DependencePluginType = Plugin<'Dependence', any, { bar: () => 'baz' }>;
```

{% endcode %}

{% code title="src/dependence/index.ts" lineNumbers="true" %}

```typescript
import { DependencePluginType } from './types';

export const DependencePlugin: DependencePluginType = {
    name: 'Dependence',
    methods: { bar: () => 'baz' },
};
```

{% endcode %}

#### Control Plane Plugin

{% code title="src/controlplane/types.ts" lineNumbers="true" %}

```typescript
import { Plugin } from '@learncard/core';

export type ControlPlanePluginType = Plugin<'Control Plane', 'id'>;
```

{% endcode %}

{% code title="src/controlplane/index.ts" lineNumbers="true" %}

```typescript
import { ControlPlanePluginType } from './types';

export const ControlPlanePlugin: ControlPlanePluginType = {
    name: 'Control Plane',
    id: {
        did: () => { throw new Error('TODO'); },
        keypair: () => { throw new Error('TODO'); },
    },
    methods: {},
}
```

{% endcode %}

#### Methods Plugin

{% code title="src/methods/types.ts" lineNumbers="true" %}

```typescript
import { Plugin } from '@learncard/core';

export type MethodsPluginMethods = {
    foo: () => 'bar';
};

export type MethodsPluginType = Plugin<'Methods', any, MethodsPluginMethods>;
```

{% endcode %}

{% code title="src/methods/index.ts" lineNumbers="true" %}

```typescript
import { MethodsPluginType } from './types';

export const MethodsPlugin: MethodsPluginType = {
    name: 'Methods',
    methods: { foo: () => 'bar' },
}
```

{% endcode %}

### Dependence Convention

As a convention, plugins will often be wrapped inside of a constructor function that requires a [LearnCard](#the-learncard-type) of a certain type be passed in.

Let's update our base plugin to see what that looks like:

<pre class="language-typescript" data-title="src/dependence/types.ts" data-line-numbers><code class="lang-typescript">import { Plugin } from '@learncard/core';

<strong>export type DependencePlugin = Plugin&#x3C;'Dependence', any, { bar: () => 'baz' }>;
</strong></code></pre>

<pre class="language-typescript" data-title="src/dependence/index.ts" data-line-numbers><code class="lang-typescript"><strong>import { LearnCard } from '@learncard/core';
</strong>import { DependencePlugin } from './types';

<strong>export const getDependencePlugin: (learnCard: LearnCard&#x3C;any>): DependencePluginType => ({
</strong>    name: 'Dependence',
    methods: { bar: () => 'baz' },
<strong>});
</strong></code></pre>

With this change, LearnCards that would like to add our plugin will now look slightly different:

```typescript
// Old
const withPlugin = await learnCard.addPlugin(DependencePlugin);

// New
const withPlugin = await learnCard.addPlugin(getDependencePlugin(learnCard));
```

### Adding dependency requirements

#### Depending on Planes

Now that we're requiring a LearnCard be passed in, we are able to add requirements to the dependent LearnCard! Let's take a look at what that looks like by attempting to take a dependency on the [Control Plane Plugin](#control-plane).

{% hint style="info" %}
**Note**: The LearnCard SDK does *not* support depending on literal plugins. It only supports depending on what plugins *implement*. In practice, this makes plugins much more flexible and easy to work with, allowing dependent plugins to be hot-swapped easily as long as they implement the same dependent methods/planes.
{% endhint %}

<pre class="language-typescript" data-title="src/dependence/index.ts" data-line-numbers><code class="lang-typescript">import { LearnCard } from '@learncard/core';
import { DependencePlugin } from './types';

<strong>export const getDependencePlugin: (learnCard: LearnCard&#x3C;any, 'id'>): DependencePluginType => {
</strong><strong>    console.log('Successfully depended on a Control Plane!', learnCard.id.did());
</strong>    
    return {
        name: 'Dependence',
        methods: { bar: () => 'baz' },
    };
};
</code></pre>

The operative change is right here on line 4:

```typescript
//                                                           VVVV
export const getDependencePlugin: (learnCard: LearnCard<any, 'id'>): DependencePluginType => {
//                                                           ^^^^
```

This change allows us to call `learnCard.id.did` on line 5, and requires consumers of this plugin to pass in a LearnCard that [implements](#implementing-control-planes) the [ID](/core-concepts/architecture-and-principles/control-planes#id-control-plane) plane when adding this plugin. For example, all of the following code will throw errors:

{% tabs %}
{% tab title="Passing in an empty wallet" %}

```typescript
import { initLearnCard } from '@learncard/init';

const learnCard = await initLearnCard({ custom: true });

const errors = await learnCard.addPlugin(getDependencePlugin(learnCard));
// TS Error: Property 'id' is missing
```

{% endtab %}

{% tab title="Passing in an incorrect wallet" %}

```typescript
import { initLearnCard } from '@learncard/init';

const learnCard = await initLearnCard();

const errors = await learnCard.addPlugin(getDependencePlugin(learnCard));
// TS Error: Property 'id' is missing
```

{% endtab %}

{% tab title="Incorrect plugin config" %}

<pre class="language-typescript"><code class="lang-typescript">// --snip--
<strong>export const getDependencePlugin: (learnCard: LearnCard&#x3C;any>): DependencePluginType => {
</strong>    console.log(learnCard.id.did());
    // TS Error: Property 'id' does not exist
</code></pre>

{% endtab %}
{% endtabs %}

#### Depending on Methods

Depending on a specific method (or methods) rather than a Control Plane looks very similar. To demonstrate this, let's stop depending on the ID Plane for a moment, and instead just depend on the `foo` method from the [Methods Plugin](#methods).

<pre class="language-typescript" data-title="src/dependence/index.ts" data-line-numbers><code class="lang-typescript">import { LearnCard } from '@learncard/core';
import { DependencePlugin } from './types';

<strong>export const getDependencePlugin: (learnCard: LearnCard&#x3C;any, any, { foo: () => 'bar' }>): DependencePluginType => {
</strong><strong>    console.log('Successfully depended on a Method!', learnCard.invoke.foo());
</strong>    
    return {
        name: 'Dependence',
        methods: { bar: () => 'baz' },
    };
};
</code></pre>

The operative change is, once again, on line 4:

```typescript
//                                                                VVVVVVVVVVVVVVVVVVVV
export const getDependencePlugin: (learnCard: LearnCard<any, any, { foo: () => 'bar' }>): DependencePluginType => {
//                                                                ^^^^^^^^^^^^^^^^^^^^
```

With this change in place, just like when we depended on a Control Plane, we are now able to call `learnCard.invoke.foo` on line 5. We also now require consumers of this plugin to pass in a LearnCard with a plugin that [implements](#implementing-methods) the `foo` method. For example, all of the following code will throw errors:

{% tabs %}
{% tab title="Passing in an empty wallet" %}

```typescript
import { initLearnCard } from '@learncard/init';

const learnCard = await initLearnCard({ custom: true });

const errors = await learnCard.addPlugin(getDependencePlugin(learnCard));
// TS Error: Property 'foo' is missing
```

{% endtab %}

{% tab title="Passing in an incorrect wallet" %}

```typescript
import { initLearnCard } from '@learncard/init';

const learnCard = await initLearnCard();

const errors = await learnCard.addPlugin(getDependencePlugin(learnCard));
// TS Error: Property 'foo' is missing
```

{% endtab %}

{% tab title="Incorrect plugin config" %}

<pre class="language-typescript"><code class="lang-typescript">// --snip--
<strong>export const getDependencePlugin: (learnCard: LearnCard&#x3C;any>): DependencePluginType => {
</strong>    console.log(learnCard.invoke.foo());
    // TS Error: Property 'foo' does not exist
</code></pre>

{% endtab %}
{% endtabs %}

### Adding Type Safety to The Implicit LearnCard

Thus far, when adding dependency requirements, we have *only* added type safety to the argument of our constructor function. This works quite well, but does *not* provide type safety to the [Implicit LearnCard](#the-implicit-learncard-object) passed into every method. To add this type safety, we use the fourth and fifth generic arguments of [the `Plugin` type](#the-plugin-type)

Let's demonstrate this by first depending on both the [Control Plane Plugin](#control-plane-plugin) *and* the [Methods Plugin](#methods-plugin), then adding some logic that uses the Implicit LearnCard to take advantage of that dependency:

<pre class="language-typescript" data-title="src/dependence/index.ts" data-line-numbers><code class="lang-typescript">import { LearnCard } from '@learncard/core';
import { DependencePlugin } from './types';

<strong>export const getDependencePlugin: (learnCard: LearnCard&#x3C;any, 'id', { foo: () => 'bar' }>): DependencePluginType => {
</strong>    return {
        name: 'Dependence',
        methods: { 
<strong>            bar: _learnCard => {
</strong><strong>                 // these two calls with throw TS errors!
</strong><strong>                 console.log('Did is:', _learnCard.id.did());
</strong><strong>                 console.log('Foo is:', _learnCard.invoke.foo());
</strong><strong>            
</strong><strong>                return 'baz';
</strong><strong>            } 
</strong>        },
    };
};
</code></pre>

Because we haven't added our dependencies to the `DependencePlugin` type itself, TS has no way of knowing that the Implicit LearnCard implements the ID Plane and the `foo` method! We can easily fix this by updating the `DependencePlugin` type:

<pre class="language-typescript" data-title="src/dependence/types.ts" data-line-numbers><code class="lang-typescript">import { Plugin } from '@learncard/core';

//                                                                             VVVVVVVVVVVVVVVVVVVVVVVVVV
<strong>export type DependencePlugin = Plugin&#x3C;'Dependence', any, { bar: () => 'baz' }, 'id', { foo: () => 'bar' }>;
</strong>//                                                                             ^^^^^^^^^^^^^^^^^^^^^^^^^^
</code></pre>

With these in place, TS will know to add the ID Plane and the `foo` method to the Implicit LearnCard, and the above errors will go away!

## Private Fields

Sometimes it is important for a Plugin to keep private state/data. This can be done using the lexical scope of the constructor function described in [Depending on Plugins](#depending-on-plugins)!

To demonstrate this, let's build a quick secret message plugin that gates a string behind a password. This plugin will use a constructor function that takes in a message and a password, exposing a `getMessage` method that will return the message if the correct password is passed in and `changePassword`/`changeMessage` methods that allow updating the password/message.

{% code title="src/types.ts" lineNumbers="true" %}

```typescript
import { Plugin } from '@learncard/core';

export type SecretMessagePluginMethods = {
    getMessage: (password: string) => string;
    changePassword: (oldPassword: string, newPassword: string) => boolean;
    changeMessage: (message: string, password: string) => boolean;
};

export type SecretMessagePlugin = Plugin<'Secret Message', any, SecretMessagePluginMethods>;
```

{% endcode %}

<pre class="language-typescript" data-title="src/index.ts" data-line-numbers><code class="lang-typescript">import { SecretMessagePlugin } from './types';

export const getSecretMessagePlugin = (message: string, password: string): SecretMessagePlugin => {
<strong>    let currentMessage = message;
</strong><strong>    let currentPassword = password;
</strong>    
    return {
        name: 'Secret Message',
        methods: {
            getMessage: (_learnCard, _password) => {
<strong>                if (_password !== currentPassword) throw new Error('Wrong password!');
</strong>                
<strong>                return currentMessage;
</strong>            },
            changePassword: (_learnCard, oldPassword, newPassword) => {
<strong>                if (oldPassword !== currentPassword) throw new Error('Wrong password!');
</strong>                
<strong>                currentPassword = newPassword;
</strong>                
                return true;
            },
            changeMessage: (_learnCard, newMessage, _password) => {
<strong>                if (_password !== currentPassword) throw new Error('Wrong password!');
</strong>                
<strong>                currentMessage = newMessage;
</strong>                
                return true;
            },
        },
    };
};
</code></pre>

This plugin can be used like so:

```typescript
const secretMessageLearnCard = await learnCard.addPlugin(getSecretMessagePlugin('nice', 'pw'));

secretMessageLearnCard.invoke.getMessage(); // Error: Wrong password!
secretMessageLearnCard.invoke.getMessage('pw') // 'nice'

secretMessageLearnCard.invoke.changePassword('pw', 'test') // true
secretMessageLearnCard.invoke.getMessage('pw') // Error: Wrong password!
secretMessageLearnCard.invoke.getMessage('test') // 'nice'

secretMessageLearnCard.invoke.changeMessage('Neat!', 'test') // true
secretMessageLearnCard.invoke.getMessage('test') // 'Neat!'
```


# Integration Strategies

Common architectural patterns for LearnCard SDK integration

This guide describes common architectural patterns for integrating LearnCard SDK into your applications, with real-world examples and implementation recommendations.

## Choosing the Right Architecture

LearnCard can be integrated into various architectures depending on your specific requirements:

| Architecture              | Best For                                          | Considerations                     |
| ------------------------- | ------------------------------------------------- | ---------------------------------- |
| Browser-based client-side | Personal wallets, user-controlled credentials     | Private key security, WASM loading |
| Server-side API           | Credential issuer services, verification services | Key management, scaling            |
| Mobile apps               | Personal wallets with native integration          | Storage, secure enclave            |
| Serverless functions      | Verification workflows, on-demand issuance        | Cold starts, timeouts              |
| Hybrid                    | Enterprise solutions with mixed requirements      | Complexity, security boundaries    |

## Pattern 1: Browser-Based Personal Wallet

This pattern uses LearnCard directly in the browser, with user-controlled keys.

### Components

1. **Frontend Application**: Typically a React/Vue/Angular SPA
2. **Browser Storage**: For secure storage of encrypted keys
3. **LearnCard Core**: Running directly in the browser
4. **External Storage Services**: Optional LearnCloud or Ceramic integration

### Implementation Example

```typescript
import { initLearnCard } from '@learncard/init';
import { deriveSeed } from '@learncard/crypto-plugin';

// User authentication flow using standard web auth
async function authenticateUser() {
  // ...login flow...
  
  // Derive a deterministic seed from user credentials
  const userKey = await deriveSeed(userId, userSecret);
  
  // Initialize LearnCard with the derived key
  const learnCard = await initLearnCard({ seed: userKey });
  
  // Store the LearnCard instance in application state
  setLearnCardInstance(learnCard);
}

// Credential management functions
async function storeCredential(credential) {
  const uri = await learnCard.store.LearnCloud.upload(credential);
  await learnCard.index.LearnCloud.add({ uri, id: credential.id });
  return uri;
}

async function getCredentials() {
  const records = await learnCard.index.LearnCloud.get();
  return Promise.all(records.map(async record => 
    learnCard.read.get(record.uri)
  ));
}
```

### Security Considerations

1. **Key Derivation**: Never store raw keys in localStorage or sessionStorage
2. **Recovery Mechanisms**: Implement key recovery flows (e.g., social recovery)
3. **Encryption**: Use additional encryption for sensitive data

## Pattern 2: Server-Side Issuer Service

This pattern uses LearnCard on a server to issue credentials to users.

### Components

1. **API Server**: Node.js/Express/FastAPI service
2. **Secure Key Storage**: Hardware Security Module (HSM) or KMS
3. **LearnCard Core**: Running on the server
4. **Database**: For tracking issued credentials

### Implementation Example

```typescript
import express from 'express';
import { initLearnCard } from '@learncard/init';
import { getSecureKey } from './keyManagement'; // Your secure key retrieval

const app = express();
app.use(express.json());

// Initialize LearnCard during service startup
let learnCard;
async function initializeService() {
  const seed = await getSecureKey();
  learnCard = await initLearnCard({ seed });
  console.log('Issuer DID:', await learnCard.invoke.getDid());
}

initializeService();

// API endpoint for credential issuance
app.post('/issue', async (req, res) => {
  try {
    const { type, subject, claims } = req.body;
    
    // Create unsigned credential
    const unsignedCredential = {
      "@context": ["https://www.w3.org/2018/credentials/v1"],
      type: ["VerifiableCredential", ...type],
      issuer: await learnCard.invoke.getDid(),
      issuanceDate: new Date().toISOString(),
      credentialSubject: {
        id: subject,
        ...claims
      }
    };
    
    // Issue the credential
    const credential = await learnCard.invoke.issueCredential(unsignedCredential);
    
    // Optionally store in LearnCloud
    const uri = await learnCard.store.LearnCloud.upload(credential);
    
    // Return both credential and URI
    res.json({ 
      credential,
      uri
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000, () => console.log('Issuer service running on port 3000'));
```

### Security Considerations

1. **Access Control**: Implement proper authentication and authorization for the API
2. **Rate Limiting**: Prevent abuse with rate limiting
3. **Audit Logging**: Keep detailed logs of all issuance operations

## Pattern 3: Mobile Wallet Integration

This pattern integrates LearnCard into mobile applications.

### Components

1. **Mobile App**: Native or React Native application
2. **Secure Storage**: Keychain/Keystore for secure key storage
3. **LearnCard Core**: Running in the mobile environment
4. **Local DB**: For caching and offline functionality

### Implementation Example (React Native)

```typescript
import { initLearnCard } from '@learncard/init';
import * as SecureStore from 'expo-secure-store';
import * as LocalAuthentication from 'expo-local-authentication';

// Key management functions
async function getOrCreateKey() {
  // Try to retrieve existing key
  let key = await SecureStore.getItemAsync('learncard_key');
  
  if (!key) {
    // Generate a new key if none exists
    key = Array.from(
      crypto.getRandomValues(new Uint8Array(32))
    ).map(b => b.toString(16).padStart(2, '0')).join('');
    
    // Require biometric authentication to store the key
    const authResult = await LocalAuthentication.authenticateAsync();
    if (authResult.success) {
      await SecureStore.setItemAsync('learncard_key', key);
    } else {
      throw new Error('Authentication required to create wallet');
    }
  }
  
  return key;
}

// Initialize LearnCard with secure key
export async function getLearnCard() {
  const key = await getOrCreateKey();
  return initLearnCard({ seed: key });
}

// Example usage in a React component
function WalletScreen() {
  const [credentials, setCredentials] = useState([]);
  const [learnCard, setLearnCard] = useState(null);
  
  useEffect(() => {
    async function initialize() {
      const lc = await getLearnCard();
      setLearnCard(lc);
      
      // Load credentials
      const records = await lc.index.all.get();
      const creds = await Promise.all(
        records.map(record => lc.read.get(record.uri))
      );
      setCredentials(creds);
    }
    
    initialize();
  }, []);
  
  // Render credential list, etc.
}
```

### Security Considerations

1. **Biometric Protection**: Use biometric authentication for key access
2. **Secure Enclave**: Utilize secure hardware when available
3. **Offline Support**: Implement proper caching for offline usage

## Pattern 4: Verification Service

This pattern implements a dedicated service for verifying credentials.

### Components

1. **API Server**: Lightweight verification endpoint
2. **LearnCard Core**: Configured with verification plugins
3. **Caching Layer**: For improved performance
4. **Monitoring**: For tracking verification metrics

### Implementation Example

```typescript
import express from 'express';
import { initLearnCard } from '@learncard/init';
import { createClient } from 'redis';

const app = express();
app.use(express.json());

// Initialize Redis for caching
const redis = createClient();
redis.connect();

// Initialize LearnCard with minimal configuration
let learnCard;
async function initializeService() {
  // For verification only, we don't need a specific seed
  learnCard = await initLearnCard();
  console.log('Verification service initialized');
}

initializeService();

// Verification endpoint
app.post('/verify', async (req, res) => {
  try {
    const { credential, options } = req.body;
    
    // Generate cache key based on credential
    const cacheKey = `verify:${JSON.stringify(credential)}`;
    
    // Check cache first
    const cachedResult = await redis.get(cacheKey);
    if (cachedResult) {
      return res.json(JSON.parse(cachedResult));
    }
    
    // Verify the credential
    const result = await learnCard.invoke.verifyCredential(credential, options);
    
    // Cache the result (only if valid, with short TTL)
    if (result.errors.length === 0) {
      await redis.set(cacheKey, JSON.stringify(result), { EX: 300 }); // 5 minutes
    }
    
    res.json(result);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(4000, () => console.log('Verification service running on port 4000'));
```

### Performance Considerations

1. **Caching**: Cache verification results for frequently verified credentials
2. **DID Resolution Caching**: Cache DID documents to reduce resolution time
3. **Horizontal Scaling**: Deploy multiple instances for high-volume scenarios

## Pattern 5: Hybrid Credential Exchange

This pattern combines elements of client and server approaches for enterprise scenarios.

### Components

1. **Client Wallet**: Browser or mobile wallet for user
2. **Issuer Service**: Server-side credential issuance
3. **Exchange Protocol**: CHAPI, QR, or direct API
4. **Verification Service**: Independent verification capability

### Implementation Example (CHAPI Exchange)

```typescript
// Server-side Issuer Component
import { initLearnCard } from '@learncard/init';
import express from 'express';

// Initialize server-side LearnCard
const issuerLearnCard = await initLearnCard({ seed: process.env.ISSUER_SEED });

app.post('/prepare-credential', async (req, res) => {
  const { subjectDid, claims } = req.body;
  
  // Create the credential for the subject
  const unsignedVC = {
    '@context': ['https://www.w3.org/2018/credentials/v1'],
    type: ['VerifiableCredential', 'Achievement'],
    credentialSubject: {
      id: subjectDid,
      ...claims
    }
  };
  
  // Issue the credential
  const vc = await issuerLearnCard.invoke.issueCredential(unsignedVC);
  
  // Generate CHAPI exchange request
  const chapiRequest = await issuerLearnCard.invoke.prepareChapiRequest(vc);
  
  res.json({ chapiRequest });
});

// Client-side Wallet Component
import { initLearnCard } from '@learncard/init';
import { installChapiHandler } from '@learncard/chapi-plugin';

async function setupChapiWallet() {
  // Initialize client-side LearnCard with secure user key
  const userLearnCard = await initLearnCard({ seed: userSeed });
  
  // Install CHAPI handler
  await installChapiHandler(userLearnCard);
  
  // Register for credential receipt
  userLearnCard.on('credentialReceived', async (credential) => {
    // Verify the credential
    const result = await userLearnCard.invoke.verifyCredential(credential);
    
    if (result.errors.length === 0) {
      // Store valid credential
      const uri = await userLearnCard.store.LearnCloud.upload(credential);
      await userLearnCard.index.LearnCloud.add({ uri, id: generateId() });
      
      // Update UI
      updateCredentialList();
    }
  });
}
```

### Architectural Considerations

1. **Authentication Flows**: Ensure proper user authentication on both sides
2. **Protocol Standards**: Adhere to standard protocols (CHAPI, DIDComm)
3. **Privacy**: Minimize data sharing between services

## Pattern 6: Enterprise Integration Hub

This pattern creates a central hub for enterprise credential issuance and verification.

### Components

1. **Admin Dashboard**: Management interface for enterprise users
2. **Key Manager**: Secure storage for multiple issuer keys
3. **Credential Service**: API for credential operations
4. **Integration Adapters**: Connectors to enterprise systems

### Implementation Example

```typescript
// Key Management Service
class EnterpriseKeyManager {
  async getIssuerKey(departmentId) {
    // Retrieve department-specific key from secure storage
    return secureStorage.getDepartmentKey(departmentId);
  }
  
  async getWalletForDepartment(departmentId) {
    const seed = await this.getIssuerKey(departmentId);
    return initLearnCard({ seed });
  }
}

// Credential Issuance API
app.post('/departments/:deptId/issue', async (req, res) => {
  const { deptId } = req.params;
  const { recipient, credential } = req.body;
  
  // Get department-specific LearnCard
  const keyManager = new EnterpriseKeyManager();
  const learnCard = await keyManager.getWalletForDepartment(deptId);
  
  // Prepare credential with department as issuer
  const deptDid = await learnCard.invoke.getDid();
  const unsignedVC = {
    ...credential,
    issuer: deptDid,
    issuanceDate: new Date().toISOString()
  };
  
  // Issue credential
  const signedVC = await learnCard.invoke.issueCredential(unsignedVC);
  
  // Store in enterprise credential registry
  await credentialRegistry.store(signedVC, deptId, recipient);
  
  // Return the signed credential
  res.json({ credential: signedVC });
});

// Integration with HR System (example adapter)
class HRSystemAdapter {
  async syncEmployeeCredentials() {
    // Get new employees/achievements from HR system
    const achievements = await hrApi.getNewAchievements();
    
    // Issue credentials for each achievement
    for (const achievement of achievements) {
      const { employeeId, departmentId, type, metadata } = achievement;
      
      // Get employee DID
      const employeeDid = await employeeDirectory.getEmployeeDid(employeeId);
      
      // Prepare credential data
      const credentialData = {
        type: ['VerifiableCredential', type],
        credentialSubject: {
          id: employeeDid,
          ...metadata
        }
      };
      
      // Issue through the credential API
      await fetch(`/departments/${departmentId}/issue`, {
        method: 'POST',
        body: JSON.stringify({
          recipient: employeeId,
          credential: credentialData
        })
      });
    }
  }
}
```

### Enterprise Considerations

1. **Compliance**: Ensure the system meets regulatory requirements
2. **Audit Trails**: Implement comprehensive logging for all operations
3. **Role-Based Access**: Restrict operations based on user roles
4. **Backup and Recovery**: Implement secure backup procedures for keys

## Conclusion

These architectural patterns provide a foundation for integrating LearnCard SDK into various application scenarios. When designing your architecture, consider:

1. **Security requirements** - especially around key management
2. **Performance needs** - for high-volume credential operations
3. **User experience** - particularly for wallet interfaces
4. **Integration points** - with existing systems and services

Each pattern can be adapted and combined to suit your specific requirements. The flexibility of LearnCard's plugin architecture allows for customization while maintaining standard credential workflows.


# Deployment

Best practices for deploying LearnCard in production environments

This guide provides comprehensive recommendations for deploying LearnCard SDK in production environments, with a focus on security, scalability, and maintainability.

## Key Management Best Practices

Proper key management is critical for production deployments of LearnCard. Your cryptographic keys are the foundation of your system's security.

### Seed Protection

Your seed phrase is the most sensitive piece of information in your LearnCard implementation. Never:

* Hard-code seeds in your application
* Store seeds in environment variables
* Commit seeds to source control
* Log seeds during application execution

Instead:

* Use a managed Key Management Service (KMS) like AWS KMS, Google Cloud KMS, or Azure Key Vault
* Implement proper access controls to your KMS
* Use Hardware Security Modules (HSMs) for highest security requirements
* Rotate keys periodically according to your security policies

```typescript
// Example using AWS KMS to retrieve a securely stored seed
import { KMS } from 'aws-sdk';
import { initLearnCard } from '@learncard/init';

async function getSecureSeed() {
  const kms = new KMS();
  const { Plaintext } = await kms.decrypt({
    CiphertextBlob: Buffer.from(process.env.ENCRYPTED_SEED, 'base64'),
    KeyId: process.env.KMS_KEY_ID
  }).promise();
  
  return Buffer.from(Plaintext).toString('hex');
}

async function initializeLearnCard() {
  const seed = await getSecureSeed();
  return initLearnCard({ seed });
}
```

## Architecture Patterns

### Serverless vs. Server-based Deployment

LearnCard can be deployed in various architectures:

**Serverless (AWS Lambda, Google Cloud Functions, etc.)**

* Pros: Auto-scaling, pay-per-use, lower operational overhead
* Cons: Cold starts impact performance, function duration limits
* Best for: Credential verification services, low-frequency issuing

**Server-based (Docker, Kubernetes, etc.)**

* Pros: Persistent connections, predictable performance, no cold starts
* Cons: Higher operational complexity, requires scaling management
* Best for: High-volume credential issuance, complex credential workflows

### Microservices Architecture

For large-scale applications, consider breaking down LearnCard functionality into dedicated microservices:

1. **Issuance Service**: Handles credential creation and signing
2. **Verification Service**: Validates credentials and presentations
3. **Storage Service**: Manages credential storage and retrieval
4. **DID Resolution Service**: Resolves DIDs and manages DID operations

This separation allows independent scaling and better resource allocation.

## Performance Optimization

### Caching Strategies

Implement caching for frequently accessed resources:

* **DID Document Caching**: Cache DID documents to reduce resolution time
* **Verification Results**: Cache verification results for frequently verified credentials
* **Template Caching**: Cache credential templates for rapid credential creation

Example Redis-based DID document caching:

```typescript
import { createClient } from 'redis';
import { initLearnCard } from '@learncard/init';

const redis = createClient();
await redis.connect();

const didPlugin = {
  name: 'cached-did-resolver',
  methods: {
    resolveDid: async (did) => {
      // Check cache first
      const cached = await redis.get(`did:${did}`);
      if (cached) return JSON.parse(cached);
      
      // If not in cache, resolve and store
      const didDocument = await originalResolveDid(did);
      await redis.set(`did:${did}`, JSON.stringify(didDocument), {
        EX: 3600 // Cache for 1 hour
      });
      
      return didDocument;
    }
  }
};

const learnCard = await initLearnCard({ 
  seed: process.env.SEED,
  plugins: [didPlugin]
});
```

### Connection Pooling

For database connections (if using external database storage):

* Use connection pooling to manage concurrent database access
* Configure appropriate pool sizes based on expected load
* Implement retry mechanisms for connection failures

## DIDKit Hosting and WASM

LearnCard makes use of the wonderful rust library [didkit](https://github.com/spruceid/didkit) under the hood. However, because didkit is written in Rust, it must first be converted to [WebAssembly](https://webassembly.org/) before it can be used in the browser, resulting in a large (\~6.6 MB) .wasm file that must be delivered to users.

The naïve approach to this would be to simply include the wasm in the initial bundle that is sent with everything else. This has the advantage of being incredibly simple to implement, however because of the size of the wasm payload, it is simply impractical to do.

To overcome this huge payload, the wasm is retrieved asynchronously when instantiating a wallet, allowing [TTFCP](https://web.dev/fcp/) to stay low while still making use of the didkit library. There are two ways to achieve this, and Learn Card gives consumers both options.

### Public Endpoint

An up-to-date copy of the wasm payload is uploaded to Learning Economy Studio's filestack, and its URL is used as a default if nothing else is provided during LearnCard construction. This has the benefit of being extremely simple for consumers to use, allowing you to not even think about didkit at all. However, this has the issue of being relatively slow in comparison to hosting the didkit wasm binary yourself.

### Hosting it Locally

The wasm binary is exposed in the npm package, and can be imported like so (depending on your specific server setup):

{% tabs %}
{% tab title="Webpack 5" %}

```typescript
import didkit from '@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm';
```

{% endtab %}

{% tab title="Vite" %}

```typescript
import didkit from '@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm?url';
```

{% endtab %}
{% endtabs %}

Doing the above will use your site's web server to host the wasm payload, allowing clients to download that payload faster (especially if you are using HTTP/2!) This method is also a bit safer. On the off-chance that `@learncard/didkit-plugin` updates the wasm payload without uploading the updated wasm and updating the default URL, you will still have the most up-to-date wasm payload!

## High Availability and Disaster Recovery

### Redundancy

* Deploy across multiple availability zones or regions
* Implement load balancing for server-based deployments
* Use managed database services with built-in replication

### Backup and Recovery

* Regularly backup critical data, including:
  * Credential indexes
  * DIDs and keys (securely)
  * Configuration data
* Test recovery procedures periodically
* Document recovery processes for operational staff

## Monitoring and Observability

### Key Metrics to Monitor

* Credential issuance rate and errors
* Verification success/failure rates
* Storage read/write operations
* API response times
* Error rates by endpoint/operation
* Resource utilization (CPU, memory, network)

### Logging Best Practices

* Use structured logging (JSON format) for easier parsing
* Include correlation IDs for tracking operations across services
* Log authentication and authorization events
* Never log sensitive information (keys, credentials, personal data)
* Implement log retention policies in compliance with regulations

Example logging configuration:

```typescript
import { initLearnCard } from '@learncard/init';
import { createLogger } from 'winston';

const logger = createLogger({
  level: 'info',
  format: combine(timestamp(), json()),
  defaultMeta: { service: 'learn-card-service' }
});

// Create a plugin that logs operations
const loggingPlugin = {
  name: 'operation-logger',
  methods: {
    issueCredential: async (credential, options, learnCard) => {
      const startTime = Date.now();
      try {
        const result = await learnCard.invoke.issueCredential(credential, options);
        logger.info('Credential issued', {
          operation: 'issueCredential',
          duration: Date.now() - startTime,
          credentialType: credential.type,
          success: true
        });
        return result;
      } catch (error) {
        logger.error('Credential issuance failed', {
          operation: 'issueCredential',
          duration: Date.now() - startTime,
          credentialType: credential.type,
          success: false,
          errorMessage: error.message
        });
        throw error;
      }
    }
  }
};
```

## Security Considerations

### Input Validation

Always validate inputs to your LearnCard API:

* Sanitize and validate all user-supplied data
* Implement schema validation for credential data
* Check for malformed DIDs and credential formats

### Rate Limiting

Implement rate limiting to prevent abuse:

* Limit credential issuance requests per API key/user
* Apply more restrictive limits for verification operations
* Use token bucket or similar algorithms for flexible rate limiting

### Audit Trails

Maintain comprehensive audit trails:

* Log all credential issuance with timestamps and issuer information
* Record verification attempts (successful and failed)
* Track access to stored credentials
* Ensure compliance with relevant regulations (GDPR, etc.)

## Upgrading and Versioning

### Semantic Versioning

Follow semantic versioning for your LearnCard implementation:

* MAJOR: Breaking changes to API
* MINOR: New features, non-breaking changes
* PATCH: Bug fixes and minor improvements

### Upgrade Strategy

* Test upgrades in staging environment before production
* Maintain comprehensive test suites to validate upgrades
* Document breaking changes and migration paths
* Consider running parallel systems during major upgrades

## Conclusion

A production-ready LearnCard deployment requires careful planning and implementation of security measures, performance optimizations, and operational practices. By following the guidelines in this document, you can build a robust, scalable, and secure credential management system using LearnCard SDK.

Remember that your specific requirements may vary based on your use case, regulatory environment, and organizational policies. Always adapt these recommendations to your particular context.


# Troubleshooting

Common issues and solutions when working with LearnCard SDK

This guide addresses common issues encountered when developing with LearnCard SDK and provides practical solutions and workarounds.

## Installation and Setup Issues

### Module Resolution Errors

**Issue:** Errors like `Cannot find module '@learncard/core'` or `Cannot find module '@learncard/init'`

**Solutions:**

1. Verify package installation:

   ```bash
   npm list @learncard/core @learncard/init
   # or for Bun
   bun pm ls @learncard/core @learncard/init
   ```
2. Check for version mismatches between LearnCard packages:

   ```bash
   npm list | grep learncard
   # or for Bun
   bun pm ls | grep learncard
   ```
3. Clear node\_modules and reinstall:

   ```bash
   rm -rf node_modules
   bun install
   ```

### WASM Loading Issues

**Issue:** Errors related to WASM loading, particularly in browsers or serverless environments

**Error messages:**

* `WebAssembly module is included in initial chunk`
* `Error: Unable to load WASM module`

**Solutions:**

1. For webpack, ensure proper WASM loading configuration:

   ```javascript
   // webpack.config.js
   module.exports = {
       experiments: {
           asyncWebAssembly: true,
       },
       // ...
   };
   ```
2. For Next.js, use dynamic imports:

   ```javascript
   const initializeLearnCard = async () => {
       const { initLearnCard } = await import('@learncard/init');
       return initLearnCard({ seed: yourSeed });
   };
   ```
3. Provide your own WASM bundle path:

   ```javascript
   import { initLearnCard } from '@learncard/init';
   import didkitPath from '@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm?url';

   const learnCard = await initLearnCard({
       seed: yourSeed,
       didkit: didkitPath,
   });
   ```

## Credential Issuance and Verification Issues

### Invalid Signature Errors

**Issue:** `Verification equation was not satisfied` or `Signature error` during credential verification

**Solutions:**

1. Ensure you're using the same wallet instance for verification that was used for issuance
2. Check for credential tampering or modification after issuance
3. Verify the credential format matches expected schema
4. Check if the credential has been properly signed:

   ```javascript
   // Verify the credential structure
   console.log(JSON.stringify(credential, null, 2));

   // Ensure the proof exists and is properly formatted
   console.log(credential.proof);
   ```

### DID Resolution Failures

**Issue:** Errors like `Unable to resolve DID` or `Not a valid DID` during verification

**Solutions:**

1. Verify the DID format:

   ```javascript
   // Should follow the pattern: did:method:identifier
   console.log(credential.issuer);
   ```
2. Check network connectivity if resolving remote DIDs
3. For `did:web`, ensure the domain is accessible and properly configured
4. Try resolving the DID manually:

   ```javascript
   const didDocument = await learnCard.invoke.resolveDid(credential.issuer);
   console.log(didDocument);
   ```

## Storage and Retrieval Issues

### URI Resolution Failures

**Issue:** Unable to retrieve credentials from URIs, getting `null` or errors

**Solutions:**

1. Check URI format:

   ```javascript
   // Should follow proper URI format for the storage method
   console.log(uri);
   ```
2. Verify network connectivity for remote storage
3. Ensure the credential exists at the specified location
4. For LearnCloud storage, check API connectivity:

   ```javascript
   const status = await fetch('https://network.learncard.com/health');
   console.log(await status.json());
   ```

### Index Retrieval Issues

**Issue:** `index.[provider].get()` returns empty array or missing credentials

**Solutions:**

1. Verify credentials were properly added to the index:

   ```javascript
   // Check all indexes
   const allIndexes = await learnCard.index.all.get();
   console.log(allIndexes);

   // Check specific index
   const learnCloudIndex = await learnCard.index.LearnCloud.get();
   console.log(learnCloudIndex);
   ```
2. Ensure you're using the same wallet instance (same seed) that added the credentials
3. Check if the storage service is accessible

## Plugin-Related Issues

### Plugin Not Found

**Issue:** Errors like `Cannot invoke method from undefined plugin: [methodName]`

**Solutions:**

1. Ensure plugin is properly registered:

   ```javascript
   // Check registered plugins
   console.log(learnCard.plugins.map(p => p.name));
   ```
2. Check plugin initialization order (dependency plugins should be loaded first)
3. Verify plugin compatibility with your LearnCard Core version

### Plugin Method Errors

**Issue:** Unexpected errors when calling plugin methods

**Solutions:**

1. Check method parameters match expected types
2. Verify plugin version compatibility with core
3. Enable debug mode if available:

   ```javascript
   const learnCard = await initLearnCard({
       seed: yourSeed,
       debug: true,
   });
   ```

## Browser/Environment-Specific Issues

### CORS Issues

**Issue:** CORS errors when accessing storage or DIDs in browser environments

**Solutions:**

1. Ensure your backend properly sets CORS headers
2. For development, consider using a CORS proxy
3. For Ceramic/LearnCloud, check endpoint configurations

### Mobile Web View Incompatibilities

**Issue:** LearnCard functionality not working in mobile web views

**Solutions:**

1. Ensure WASM is supported in the web view
2. Check for any mobile-specific storage limitations
3. Consider using LearnCard Bridge HTTP API for mobile apps

## Debugging Techniques

### Enable Verbose Logging

Add a logging plugin to capture detailed information:

```javascript
const loggingPlugin = {
    name: 'debug-logger',
    methods: {
        intercept: async (method, params, learnCard) => {
            console.log(`Calling ${method} with params:`, params);
            try {
                const result = await learnCard.invoke[method](...params);
                console.log(`${method} result:`, result);
                return result;
            } catch (error) {
                console.error(`${method} error:`, error);
                throw error;
            }
        },
    },
};

const learnCard = await initLearnCard({
    seed: yourSeed,
    plugins: [loggingPlugin],
});

// Now wrap any method call
const result = await learnCard.invoke.intercept('verifyCredential', [credential]);
```

### Inspect Credential Structure

For verification issues, inspect the credential structure:

```javascript
function inspectCredential(credential) {
    console.log('===== CREDENTIAL INSPECTION =====');
    console.log('Types:', credential.type);
    console.log('Issuer:', credential.issuer);
    console.log('Subject:', credential.credentialSubject.id);
    console.log('Proof type:', credential.proof?.type);
    console.log('Issuance date:', credential.issuanceDate);
    console.log('Expiration date:', credential.expirationDate);
    console.log('================================');
}
```

## Getting Help

If you continue to experience issues after trying the solutions in this guide:

1. Check the [GitHub repository](https://github.com/learningeconomy/LearnCard) for open issues
2. Join the [Learning Economy Discord](https://discord.gg/learningeconomy) for community support
3. Submit a detailed bug report with:
   * LearnCard SDK version
   * Node.js/browser version
   * Complete error message and stack trace
   * Minimal reproducible example
   * Environment details (OS, deployment context)


# Changelog

Did you upgrade and something broke? Here's how to fix it!

We try not to needlessly add breaking changes, but sometimes it can happen! To find out how to migrate between versions, follow this guide.

> “Love doesn't just sit there, like a stone, it has to be made, like bread; remade all the time, made new.” **- Ursula Le Guin, the Lathe of Heaven**

## 8.0 -> 9.0

### BREAKING CHANGE: @learncard/core package split

#### Breaking Changes

* `initLearnCard` is no longer exported by `@learncard/core`, as it is now the responsibility of `@learncard/init`

```typescript
// Old
import { initLearnCard } from '@learncard/core';

// New
import { initLearnCard } from '@learncard/init';
```

* The didkit wasm binary is no longer exported by `@learncard/core`, as it is now the responsibility of `@learncard/init`

```typescript
// Old
import didkit from '@learncard/core/dist/didkit/didkit_wasm_bg.wasm';

// New
import didkit from '@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm';
```

* `@learncard/network-plugin` and `@learncard/did-web-plugin` no longer export their own version of `initLearnCard`, and are instead now proper instantiation targets from `@learncard/init`

```typescript
// Old
import { initNetworkLearnCard } from '@learncard/network-plugin';
import { initDidWebLearnCard } from '@learncard/did-web-plugin';

const networkLearnCard = await initNetworkLearnCard({ seed: 'a'.repeat(64) });
const didWebLearnCard = await initDidWebLearnCard({ seed: 'a'.repeat(64), didWeb: 'did:web:test' });

// New
import { initLearnCard } from '@learncard/init';

const networkLearnCard = await initLearnCard({ seed: 'a'.repeat(64), network: true });
const didWebLearnCard = await initLearnCard({ seed: 'a'.repeat(64), didWeb: 'did:web:test' });
```

### Migration Steps

For the most part, you can just rename `@learncard/core` to `@learncard/init`, and everything should just work, with the exceptions listed above! In the rare case that doesn't immediately work, you *may* need to import something directly from the plugin itself, such as the case with the didkit wasm binary.

### Notes

With this change, `@learncard/core` becomes version 9.0, however it also becomes much less public facing compared to the newly released `@learncard/init` (version 1.0). This documentation will continue to use the `@learncard/core` versioning.

## 7.0 -> 8.0

### BREAKING CHANGE: Control Planes Overhaul

#### Breaking Changes

* Universal Wallets are now returned directly by [`initLearnCard`](broken://pages/br6yNgOwLe1zZAxr9kpC), rather than a wrapped object.
  * This means you can now call [`addPlugin`](broken://pages/8H0hMQTbMTdDcpSZnw1v) directly on the return value of `initLearnCard`:

```typescript
const learnCard = await initLearnCard();
const bespokeLearnCard = await learnCard.addPlugin(plugin);
```

* Universal Wallets now implement[ *Control Planes*](/core-concepts/architecture-and-principles/control-planes) as well as just methods - These are top-level objects with standardized functions that allow plugins/consumers access to a unified interface for common operations. When it makes sense, a specific plugin implementing a plane can also be chosen, such as choosing where to store a credential when uploading. - There are currently five planes, with more planned for the future:
  * [**Read**](broken://pages/s13FerGAbkHnrN13v85i), which implements `get`
    * `get` simply resolves a URI to a VC
  * [**Store**](broken://pages/AacoBhj2Q6kUaK1EVNnj), which implements `upload` and (optionally) `uploadMany`
    * `upload` stores a VC and returns a URI that can be resolved
    * `uploadMany` stores an array of VCs, returning an array of URIs that can be resolved
  * [**Index**](broken://pages/plqXXrKGsTzgtcePKceH), which implements `get`, `add`, `update`, and `remove`
    * `get` returns a list of the holder's credential objects (currently named `IDXCredential`s)
      * These objects contain (at a minimum) an `id` and a `uri` that can be resolved to a VC
    * `add` adds a credential to the holder's list
    * `update` updates an object in the holder's list
    * `remove` removes an object from the holder's list
  * [**Cache**](broken://pages/gwMHPTurOKzUunsu7wxm), which implements `getIndex`, `setIndex`, `flushIndex`, `getVc`, `setVc`, and `flushVc`
    * `getIndex` returns the hodler's credential list as it exists in the cache
    * `setIndex` sets the holder's credential list in the cache
    * `flushIndex` emptys the holder's credential list cache
    * `getVc` returns a VC for a URI if it exists in the cache
    * `setVc` sets a VC for a URI in the cache
    * `flushVc` emptys all VCs from the cache
  * [**ID**](broken://pages/IxLd9QFu08nEXrI65rwA)**,** which implements `did` and `keypair`
    * `did` is identical to the previous `wallet.did` method, returning a did for a given method
    * `keypair` is identical to the previous `wallet.keypair` method, returning a JWK for a given cryptographic algorithm
* Plugins implement planes via the second generic parameter to the `Plugin` type
  * For example, a plugin implementing the Read and Store planes would be typed like this: `Plugin<'Test', 'read' | 'store'>`
* Plugins may continue to expose methods the same way they have, instead using the third generic parameter instead of the second:
  * For example, a plugin implementing the `getSubjectDid` method would be typed like this: `Plugin<'Test', any, { getSubjectDid: (did?: string) => string }>`
* Plugins may depend on wallets that implement planes/methods in the same way
  * For example, a wallet implementing the id plane and the `getSubjectDid` method may be typed like this: `Wallet<any, 'id', { getSubjectDid: (did?: string) => string }>`
* The `pluginMethods` key has been renamed to `methods` when creating a plugin, and `invoke` when calling them from a wallet
* The old `LearnCard` type has been removed
* The `Wallet` type has been renamed to `LearnCard`
* `generateWallet` has been renamed to `generateLearnCard`
  * This function should now be considered private. If you'd like to construct a fully custom LearnCard, please use [`initLearnCard({ custom: true })`](/sdks/learncard-core/construction) instead
* The `did` method now has had its type loosened to just `string`
* The `verifyCredential` method now returns a `VerificationCheck` directly, unless you explicitly ask for the prettified version via a flag
  * I.e. `wallet.verifyCredential(vc)` is now `c(vc, {}, true)`
* The `name` field is now *required* for plugins, and they may optionally specify a `displayName` and `description`
* `walletFromKey` has been renamed to `learnCardFromSeed`
* `walletFromApiUrl` has been renamed to `learnCardFromApiUrl`
* `emptyWallet` has been renamed to `emptyLearnCard`

### `Migration Steps`

For the most part, you can simply rename calls and everything will just work

* `wallet.did` is now `wallet.id.did`
* `wallet.keypair` is now `wallet.id.keypair`
* `wallet.newCredential` is now `wallet.invoke.newCredential`
* `wallet.newPresentation` is now `wallet.invoke.newPresentation`
* `wallet.verifyCredential` is now `wallet.invoke.verifyCredential`
  * As per above, if you'd like to retain the same output format, you will need to pass true as the third argument
    * I.e. `wallet.verifyCredential(vc)` is now `wallet.invoke.verifyCredential(vc, {}, true)`
* `wallet.issueCredential` is now `wallet.invoke.issueCredential`
* `wallet.issuePresentation` is now `wallet.invoke.issuePresentation`
* `wallet.verifyPresentation` is now `wallet.invoke.verifyPresentation`
* `wallet.getCredential` is *a bit* more complex, as it was actually wrapping two operations:
  * Getting a credential with a given id
  * Resolving that credential
  * This can now be replaced with the following code:

```typescript
// Old
const vc = await lc.getCredential('test');

// New
const record = (await lc.index.all.get()).find(record => record.id === 'test');
const vc = await lc.read.get(record.uri);
```

If this proves to be too cumbersome, we may add back in the `getCredential` method as helper in `invoke`

* `wallet.getCredentials` has the same issue as `wallet.getCredential`, it can be replaced with the following code:

```typescript
const vcs = await lc.getCredentials();

// New
const uris = (await lc.index.all.get()).map(record => record.uri);
const vcs = await Promise.all(uris.map(async uri => lc.read.get(uri)));
```

* `wallet.getCredentialsList` is now `wallet.index.all.get`
* `wallet.publishCredential` is now `wallet.store.Ceramic.upload`
* `wallet.addCredential` is now `wallet.index.IDX.add`
* `wallet.removeCredential` is now `wallet.index.IDX.remove`
* `wallet.resolveDid` is now `wallet.invoke.resolveDid`
* `wallet.readFromCeramic` is now `wallet.invoke.readContentFromCeramic`
* `wallet.resolveCredential` is now `wallet.read.get`
* `wallet.getTestVc` is now `wallet.invoke.getTestVc`
* `wallet.getTestVp` is now `wallet.invoke.getTestVp`
* `wallet.getEthereumAddress` is now `wallet.invoke.getEthereumAddress`
* `wallet.getBalance` is now `wallet.invoke.getBalance`
* `wallet.getBalanceForAddress` is now `wallet.invoke.getBalanceForAddress`
* `wallet.transferTokens` is now `wallet.invoke.transferTokens`
* `wallet.getCurrentNetwork` is now `wallet.invoke.getCurrentNetwork`
* `wallet.changeNetwork` is now `wallet.invoke.changeNetwork`
* `wallet.addInfuraProjectId` is now `wallet.invoke.addInfuraProjectId`
* `wallet.vpFromQrCode` is now `wallet.invoke.vpFromQrCode`
* `wallet.vpToQrCode` is now `wallet.invoke.vpToQrCode`
* `wallet.installChapiHandler` is now `wallet.invoke.installChapiHandler`
* `wallet.activateChapiHandler` is now `wallet.invoke.activateChapiHandler`
* `wallet.receiveChapiEvent` is now `wallet.invoke.receiveChapiEvent`
* `wallet.storePresentationViaChapi` is now `wallet.invoke.storePresentationViaChapi`
* `wallet.storeCredentialViaChapiDidAuth` is now `wallet.invoke.storeCredentialViaChapiDidAuth`

Because the `LearnCard` and `Wallet` types have changed, if you were using either, you will need to update your code:

```typescript
let test: LearnCard | undefined = undefined; // Old

let test: LearnCardFromSeed['returnValue'] | undefined = undefined; // New
let test: LearnCard<any> | undefined = undefined; // Also valid if you don't know which instantiation function will be used

let test: Wallet<any, { getSubjectDid: (did?: string) => string }>; // Old

let test: LearnCard<any, any, { getSubjectDid: (did?: string) => string }>; // New
```

### Notes

We are moving away from the term "wallet", preferring instead to use the term LearnCard in its place. You may notice that in updated documentation and examples, variables named `wallet` have been replaced with the name `learnCard`.


# LearnCloud Network API

The **LearnCloud Network** provides backend infrastructure for managing verifiable credentials, digital identities, user profiles, and consent flows in the LearnCard ecosystem.

This API provides a set of convenient endpoints that allow you to manage credentials, boosts, and presentations associated with user profiles. Here's a quick high-level summary of what you can do with the API:

1. **Profile Management**: Create, update, and search user profiles
2. **Credentials**: Send, accept, retrieve, and delete credentials between users on the LearnCard Network. Credentials can be used to represent skills, achievements, or other information related to a user's profile.
3. **Boosts**: Create, send, update, delete, and claim boosts for users on the network. Boosts are a way to reward and incentivize user interactions, such as completing tasks or participating in events.
4. **Presentations**: Send, accept, retrieve, and delete presentations between users. Presentations are packages of credentials that can be shared with others to demonstrate the user's skills or achievements.
5. **Consent Flow**: Manage consent contracts for data sharing and credential issuance
6. **DID Web Resolution**: Provide DID Web identity resolution
7. **Notification System**: Send notifications for various events
8. **Utilities**: Check the health of the API endpoint, retrieve a list of valid challenges, and obtain the LearnCard Network Decentralized Identifier (DID).
9. **Storage**: Store and resolve credentials or presentations using a unique identifier (URI).
10. **Skills Semantic Search**: Generate embeddings for skill content and query skills by semantic similarity.
11. **OpenSALT Skill Frameworks**: Link OpenSALT/CASE frameworks, sync skills, and reuse standards in boost alignment flows.

**The LearnCard Network API** makes it simple to create engaging and interactive applications that utilize credentials, boosts, and presentations between users and applications.


# Authentication

### Authenticating with the LearnCard Network API

To interact with the LearnCard Network API, you can choose one of two ways to authenticate:

1. Using the LearnCard Network Plugin (`@learncard/network-plugin`) which handles authentication for you. **(Preferred option)**
2. Using[ a scoped API Token](/how-to-guides/deploy-infrastructure/generate-api-tokens) for authentication with API endpoints. (recommended for implementations using REST endpoints).
3. Directly through the API endpoints using challenge-based DID Authentication. (most complex)

#### 1. Using LearnCard Network Plugin

To authenticate using the LearnCard Network Plugin (`@learncard/network-plugin`), first install the package:

```bash
bun add @learncard/network-plugin
```

Then, either instantiate a LearnCard Network enabled LearnCard, or add the Network Plugin to an existing LearnCard:

{% tabs %}
{% tab title="Direct Instantiation" %}
{% code lineNumbers="true" %}

```typescript
import { initLearnCard } from '@learncard/init';
import didkit from '@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm?url';

const networkLearnCard = await initLearnCard({
    seed,
    network: true,
    didkit,
});
```

{% endcode %}
{% endtab %}

{% tab title="Add Plugin" %}

```typescript
import { initLearnCard } from '@learncard/init';
import { getLearnCardNetworkPlugin } from '@learncard/network-plugin';
import didkit from '@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm?url';

const lcnAPI = 'https://network.learncard.app/trpc';

const learnCard = await initLearnCard({
    seed,
    didkit,
});

const networkLearnCard = await learnCard.addPlugin(
    await getLearnCardNetworkPlugin(learnCard, lcnAPI)
);
```

{% endtab %}
{% endtabs %}

When using the LearnCard Network Plugin, challenge-based DID Authentication is handled for you, so no further steps are necessary.

#### Guardian-gated routes (child / managed profiles)

Some Network routes are **guardian-gated** for child (managed) profiles. When calling those routes, the client can optionally include a guardian approval token in an `x-guardian-approval` header.

This header value is a **JWT Verifiable Presentation** (VP) created by a guardian profile, and it is validated server-side. If the header is missing or invalid, the route will behave as if `hasGuardianApproval = false`.

If you want the Network Plugin to attach this header automatically, pass a `guardianApprovalGetter` option:

```typescript
const networkLearnCard = await learnCard.addPlugin(
    await getLearnCardNetworkPlugin(learnCard, lcnAPI, {
        guardianApprovalGetter: async () => {
            // Return a JWT VP string (or undefined if no approval is available)
            return undefined;
        },
    })
);
```

#### 2. Using a [scoped API Token](/how-to-guides/deploy-infrastructure/generate-api-tokens)

```typescript
// Step 1: Create an AuthGrant with specific permissions
const grantId = await learnCard.invoke.addAuthGrant({
    name: 'Boost Sender Auth',
    description: 'Permission to send boosts',
    scope: 'boosts:write',
});

// Step 2: Generate an API token from the AuthGrant
const token = await learnCard.invoke.getAPITokenForAuthGrant(grantId);

// Step 3: Prepare the payload for your API request
const payload = {
    boostUri: 'uri-of-the-boost-to-send',
    signingAuthority: 'your-signing-authority',
};

// Step 4: Make an authenticated HTTP request using the token
const response = await fetch(
    `https://network.learncard.com/api/boost/send/via-signing-authority/RECIPIENT_PROFILE_ID`,
    {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${token}`,
            // Optional: include guardian approval when calling guardian-gated routes
            // 'x-guardian-approval': guardianApprovalJwtVp,
        },
        body: JSON.stringify(payload),
    }
);

// Step 5: Process the response
if (response.status === 200) {
    const sentBoostUri = await response.json();
    console.log(`Boost sent successfully: ${sentBoostUri}`);
} else {
    console.error(`Error sending boost: ${response.status}`);
    const errorDetails = await response.json();
    console.error(errorDetails);
}
```

#### 3. Using Challenge-based DID Authentication

Profile management uses DID-based authentication with a challenge-response mechanism and scope-based authorization.

**Simple High-Level Auth Flow:**

```mermaid
sequenceDiagram
    participant Client as Client
    participant Network as Network API
          Client->>Network: Request access
          Network->>Client: Provide challenge
          Client->>Client: Sign challenge with DID
          Client->>Network: Submit signed challenge
          Network->>Network: Verify signature
          Network->>Client: Grant authenticated access
```

**Granular Auth Flow:**

```mermaid
sequenceDiagram
    participant Client
    participant BrainService as "Network API"
    participant Cache as "Redis Cache"

    Client->>BrainService: utilities.getChallenges()
    BrainService-->>Client: [challenge1, challenge2, ...]

    Client->>Client: Sign challenge with DID
    Client->>BrainService: Request with signed challenge in Authorization header

    BrainService->>BrainService: Extract DID from JWT
    BrainService->>Cache: Check if challenge is valid for DID
    Cache-->>BrainService: Validation result

    alt Valid challenge
        BrainService->>Cache: Invalidate challenge
        BrainService->>BrainService: Process request
        BrainService-->>Client: Response
    else Invalid challenge
        BrainService-->>Client: 401 Unauthorized
    end
```

If you choose to use the API endpoints directly, you'll need to manage challenge-based DID Authentication for each request. Here's a simplified TypeScript example to help you implement this authentication method:

```typescript
async function getClient(
    url: string = 'https://network.learncard.com/api',
    didAuthFunction: (challenge?: string) => Promise<string>,
    guardianApprovalGetter?: () => string | undefined | Promise<string | undefined>
) {
    let challenges: string[] = [];

    const getChallenges = async (
        amount = 95 + Math.round((Math.random() - 0.5) * 5)
    ): Promise<string[]> => {
        // Call the API to get a list of challenges
        // Replace this line with your preferred way of making API calls
        const response = await fetch(url + '/challenges?amount=' + amount);
        return await response.json();
    };

    challenges = await getChallenges();

    async function getAuthHeaders() {
        if (challenges.length === 0) challenges.push(...(await getChallenges()));
        const guardianApproval = guardianApprovalGetter
            ? await guardianApprovalGetter()
            : undefined;
        return {
            Authorization: `Bearer ${await didAuthFunction(challenges.pop())}`,
            ...(guardianApproval ? { 'x-guardian-approval': guardianApproval } : {}),
        };
    }

    // Use getAuthHeaders in your API calls to set the Authorization header
}

export default getClient;
```

In this example, we first define a `getClient` function that takes a `url` and a `didAuthFunction`. The `didAuthFunction` should be an asynchronous function that returns a signed challenge as a string.

The `getChallenges` function fetches a list of challenges from the API. The `getAuthHeaders` function generates an Authorization header using the `didAuthFunction` and a challenge. This header can then be used in your API calls.

## Authorization & Scopes

#### Route Middleware <a href="#route-middleware" id="route-middleware"></a>

The system uses several middleware layers for authentication and authorization:

1. `openRoute`: Base middleware that allows public access
2. `didRoute`: Requires a valid DID in the request
3. `didAndChallengeRoute`: Requires a valid DID and challenge
4. `profileRoute`: Requires a valid DID, challenge, and existing profile
5. `scopedRoute`: Requires specific permission scopes

#### Authorization Scopes <a href="#authorization-scopes" id="authorization-scopes"></a>

Each API endpoint requires specific scopes for authorization:

| Scope                      | Description                | Example Endpoints                |
| -------------------------- | -------------------------- | -------------------------------- |
| `profiles:read`            | Read profile information   | `getProfile`, `getOtherProfile`  |
| `profiles:write`           | Create or update profiles  | `createProfile`, `updateProfile` |
| `profiles:delete`          | Delete profiles            | `deleteProfile`                  |
| `connections:read`         | View connections           | `paginatedConnections`           |
| `connections:write`        | Manage connections         | `connectWith`, `blockProfile`    |
| `signingAuthorities:read`  | View signing authorities   | `signingAuthorities`             |
| `signingAuthorities:write` | Manage signing authorities | `registerSigningAuthority`       |

The authorization system also supports wildcard scopes like `*:read` (read access to all resources) and `*:*` (full access).


# Usage Examples

This page provides common usage examples for the **LearnCloud Network API**, so you can quickly see how to

* Send and receive credentials, boosts, and presentations
* Create and claim credentials through peer-to-peer or QR flows
* Register and manage Signing Authorities
* Trigger and validate ConsentFlows
* Monitor health and fetch metadata (like DIDs or challenge keys)
* Run semantic search across skill frameworks
* Link and use OpenSALT skill frameworks

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://network.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.

***

## 📤 Sending Credentials

### The `send` Method (Recommended)

The simplest way to send credentials is using the `send` method, which handles credential issuance, signing, and delivery in a single call.

#### Using an Existing Boost Template

```typescript
const result = await learnCard.invoke.send({
    type: 'boost',
    recipient: 'recipient-profile-id', // Profile ID or DID
    templateUri: 'urn:lc:boost:abc123',
});

// Returns: { type: 'boost', credentialUri: '...', uri: '...' }
```

#### Creating and Sending a New Boost

```typescript
const result = await learnCard.invoke.send({
    type: 'boost',
    recipient: 'recipient-profile-id',
    template: {
        credential: unsignedVC,
        name: 'Course Completion Certificate',
        category: 'Achievement',
    },
});
```

#### With ConsentFlow Contract

```typescript
const result = await learnCard.invoke.send({
    type: 'boost',
    recipient: 'recipient-profile-id',
    templateUri: 'urn:lc:boost:abc123',
    contractUri: 'urn:lc:contract:xyz789', // Routes via consent terms if applicable
});
```

{% hint style="info" %}
**Signing Behavior**: The `send` method uses client-side signing when key material is available. Otherwise, it falls back to your registered signing authority.
{% endhint %}

***

## 🔎 Semantic Skill Search

Use semantic search when keyword matching is too strict and you want meaning-based results.

```typescript
const results = await learnCard.invoke.semanticSearchSkills({
    text: 'hands-on robotics troubleshooting',
    frameworkId: 'framework-123', // optional
    limit: 50, // optional (default 50)
});

console.log(results.records[0]);
// {
//   id: 'skill-id',
//   statement: 'Diagnose wiring faults',
//   description: '...',
//   frameworkId: 'framework-123',
//   score: 0.88,
//   ...
// }
```

{% hint style="info" %}
Semantic search requires skill embeddings to be present. In LearnCard Network, embeddings are generated for skill create/update/sync flows and can be backfilled when enabled by environment configuration.
{% endhint %}

***

## OpenSALT Skill Frameworks

Link an OpenSALT framework by CASE URL (or UUID), then sync it locally:

```typescript
const framework = await learnCard.invoke.createSkillFramework({
    frameworkId:
        'https://opensalt.net/ims/case/v1p0/CFDocuments/c6085394-d7cb-11e8-824f-0242ac160002',
});

await learnCard.invoke.syncFrameworkSkills({ id: framework.id });
```

List frameworks available to the current profile (managed + public):

```typescript
const page = await learnCard.invoke.getAllAvailableFrameworks({
    limit: 25,
    cursor: null,
});

console.log(page.records.map(framework => framework.id));
```

To align a boost with skills from that framework:

```typescript
await learnCard.invoke.alignBoostSkills(boostUri, [{ frameworkId: framework.id, id: 'skill-id' }]);
```

For full details, see [Skill Frameworks & OpenSALT](/sdks/learncard-network/skills-and-opensalt).

***

## 📱 App Store Credentials

### Fetch Credentials Sent by an App

Retrieve credentials that a specific app (App Store listing) has sent to the current user. Useful for building in-app credential dashboards.

```typescript
const result = await learnCard.invoke.getMyCredentialsFromApp(
    'my-app-listing-id', // App Store listing ID or slug
    { limit: 50 } // Optional pagination
);

console.log(result);
// {
//   hasMore: false,
//   cursor: 'abc123',
//   totalCount: 3,
//   records: [
//     {
//       credentialId: 'cred-123',
//       credentialUri: 'lc:credential:...',
//       date: '2024-03-09T12:00:00Z',
//       status: 'claimed',
//       boostName: 'Course Completion Badge',
//       boostCategory: 'Achievement'
//     },
//     ...
//   ]
// }
```

#### Credential Status Values

| Status    | Description                                 |
| --------- | ------------------------------------------- |
| `pending` | Credential sent but not yet claimed by user |
| `claimed` | User has claimed the credential             |
| `revoked` | Credential has been revoked by issuer       |

#### Resolving Full Credential Data

The API returns `credentialUri` which can be used to fetch the full Verifiable Credential:

```typescript
for (const record of result.records) {
    const fullVC = await learnCard.read.get(record.credentialUri);
    console.log(fullVC.credentialSubject);
}
```

{% hint style="info" %}
**Pagination**: Use `cursor` from the response to fetch additional pages. The `hasMore` flag indicates if more records exist.
{% endhint %}


# Bitstring Status Lists

LearnCard Network can add W3C Bitstring Status List entries to VC 2.0 credentials. Status entries let verifiers check revocation and suspension without changing the signed credential.

```typescript
import { initLearnCard } from '@learncard/init';

const issuer = await initLearnCard({ seed: 'issuer seed', network: true });
const verifier = await initLearnCard({ seed: 'verifier seed', network: true });
```

## Send a Boost With Status

Network-issued VC 2.0 Boost credentials receive both revocation **and** suspension status entries by default, so both states are externally verifiable from issuance.

```typescript
const credentialUri = await issuer.invoke.sendBoost(recipientProfileId, boostUri);
```

To allocate only a subset of status purposes, pass `statusPurposes` (e.g. revocation only):

```typescript
const credentialUri = await issuer.invoke.sendBoost(recipientProfileId, boostUri, {
    statusPurposes: ['revocation'],
});
```

Supported status purposes are:

* `revocation`
* `suspension`

## Allocate Status for Custom Issuance

Use `allocateCredentialStatus` when you are building and signing a credential directly.

```typescript
const statusEntries = await issuer.invoke.allocateCredentialStatus({
    statusPurposes: ['revocation', 'suspension'],
});

const unsignedCredential = {
    '@context': ['https://www.w3.org/ns/credentials/v2'],
    type: ['VerifiableCredential'],
    issuer: issuer.id.did(),
    validFrom: new Date().toISOString(),
    credentialSubject: {
        id: recipientDid,
    },
    credentialStatus: statusEntries,
};

const signedCredential = await issuer.invoke.issueCredential(unsignedCredential);
```

The returned entries use this shape:

```typescript
type BitstringStatusListEntry = {
    type: 'BitstringStatusListEntry';
    statusPurpose: 'revocation' | 'suspension';
    statusListIndex: string;
    statusListCredential: string;
    id?: string;
};
```

`listSize` can be passed for direct allocation. The default is `131,072` bits.

```typescript
const [statusEntry] = await issuer.invoke.allocateCredentialStatus({
    statusPurposes: ['revocation'],
    listSize: 131_072,
});
```

## Revoke, Suspend, and Unsuspend

Revocation and suspension update the issuer's status list and re-sign the status list credential.

```typescript
await issuer.invoke.revokeBoostRecipient(boostUri, recipientProfileId);

await issuer.invoke.suspendBoostRecipient(boostUri, recipientProfileId);

await issuer.invoke.unsuspendBoostRecipient(boostUri, recipientProfileId);
```

Revocation sets the `revocation` bit. Suspension sets the `suspension` bit. Unsuspension clears only the `suspension` bit.

These methods work for pending credentials as well as accepted credentials, because status is tracked on the issued credential relationship.

## Public Status List Endpoint

Each status entry points to a public status list credential:

```typescript
const statusEntry = Array.isArray(signedCredential.credentialStatus)
    ? signedCredential.credentialStatus[0]
    : signedCredential.credentialStatus;

const response = await fetch(statusEntry.statusListCredential);
const statusListCredential = await response.json();
```

The endpoint has this form:

```http
GET /status-lists/:id
```

The response is a signed VC. The `encodedList` field is the compressed bitstring used by verifiers.

```json
{
    "@context": ["https://www.w3.org/ns/credentials/v2"],
    "id": "https://network.learncard.com/status-lists/...",
    "type": ["VerifiableCredential", "BitstringStatusListCredential"],
    "issuer": "did:web:network.learncard.com",
    "validFrom": "2026-05-08T00:00:00.000Z",
    "credentialSubject": {
        "id": "https://network.learncard.com/status-lists/...#list",
        "type": "BitstringStatusList",
        "statusPurpose": "revocation",
        "encodedList": "u..."
    },
    "proof": {}
}
```

## Verify Status

Raw verification includes structured status check results.

```typescript
const rawVerification = await verifier.invoke.verifyCredential(signedCredential);

console.log(rawVerification.status);
```

Example status result:

```json
[
    {
        "entryType": "BitstringStatusListEntry",
        "statusPurpose": "revocation",
        "statusListCredential": "https://network.learncard.com/status-lists/...",
        "statusListIndex": "42",
        "isSet": false
    }
]
```

Use prettified verification output for user-facing display.

```typescript
const prettyVerification = await verifier.invoke.verifyCredential(signedCredential, {}, true);
```

Prettified status messages include:

* `Status: Active`
* `Status: Not Revoked`
* `Status: Revoked`
* `Status: Suspended`
* `Status: Not Suspended`

## Rollover

Each issuer gets status lists scoped by status purpose and list size. When a list reaches its configured size, LearnCard Network automatically creates a new list. Existing credentials keep pointing at their original status list URL.

New credentials receive entries for the current open list.


# Key Management & QR Login API

LCA API routes for SSS key management and QR login

The LCA API (`lca-api`) provides server-side routes for SSS key share storage, recovery method management, and cross-device QR login. These routes are consumed by the [SSS Key Manager](/sdks/sss-key-manager) client library.

## Base URL

The key management routes are served under the same base URL as the rest of the LCA API (e.g., `https://api.example.com/api`).

***

## Authentication

Routes use two authentication mechanisms:

* **Auth Token** — a Firebase (or other provider) ID token, passed in the request body as `authToken` + `providerType`. The server verifies the token and extracts the user's contact method.
* **DID Auth** — a signed DID-Auth Verifiable Presentation (VP) JWT, passed as `Authorization: Bearer <jwt>`. Used for routes that modify key data.

Routes marked **open** require an auth token in the body but no DID-Auth header. Routes marked **DID** require both.

***

## Key Management Routes (`/keys/*`)

### Get Auth Share

Retrieve the encrypted auth share for the authenticated user.

|              |                         |
| ------------ | ----------------------- |
| **Endpoint** | `POST /keys/auth-share` |
| **Auth**     | Auth Token (body)       |

**Request Body:**

```json
{
    "authToken": "firebase-id-token",
    "providerType": "firebase",
    "contactMethod": { "type": "email", "value": "user@example.com" },
    "shareVersion": 2
}
```

* `shareVersion` (optional) — request a specific historical share version. If omitted, returns the latest.

**Response (200):**

```json
{
    "exists": true,
    "authShare": {
        "encryptedData": "...",
        "encryptedDek": "...",
        "iv": "..."
    },
    "securityLevel": "enhanced",
    "recoveryMethods": [{ "type": "passkey", "createdAt": "2025-01-15T..." }],
    "maskedRecoveryEmail": "u***@example.com",
    "shareVersion": 2,
    "primaryDid": "did:key:z6Mk..."
}
```

***

### Store Auth Share

Store or rotate the encrypted auth share. Requires DID-Auth.

|              |                        |
| ------------ | ---------------------- |
| **Endpoint** | `PUT /keys/auth-share` |
| **Auth**     | DID Auth (header)      |

**Request Body:**

```json
{
    "authToken": "firebase-id-token",
    "providerType": "firebase",
    "contactMethod": { "type": "email", "value": "user@example.com" },
    "authShare": {
        "encryptedData": "...",
        "encryptedDek": "...",
        "iv": "..."
    },
    "primaryDid": "did:key:z6Mk...",
    "keyProvider": "sss"
}
```

**Response (200):**

```json
{ "success": true }
```

***

### Add Recovery Method

Add a recovery method (passkey, backup, phrase, or email). Requires DID-Auth.

|              |                       |
| ------------ | --------------------- |
| **Endpoint** | `POST /keys/recovery` |
| **Auth**     | DID Auth (header)     |

**Request Body:**

```json
{
    "authToken": "firebase-id-token",
    "providerType": "firebase",
    "type": "passkey",
    "credentialId": "base64url-credential-id",
    "encryptedShare": "encrypted-share-data",
    "shareVersion": 2
}
```

**Response (200):**

```json
{ "success": true }
```

***

### Get Recovery Share

Retrieve an encrypted recovery share by type and credential ID.

|              |                      |
| ------------ | -------------------- |
| **Endpoint** | `GET /keys/recovery` |
| **Auth**     | Query parameters     |

**Query Parameters:**

* `contactMethodType` — `email` or `phone`
* `contactMethodValue` — the email or phone number
* `type` — recovery method type (e.g., `passkey`)
* `credentialId` — the passkey credential ID

**Response (200):**

```json
{
    "encryptedShare": "...",
    "shareVersion": 2
}
```

***

### Add Recovery Email

Send a 6-digit verification code to the specified email address.

|              |                                 |
| ------------ | ------------------------------- |
| **Endpoint** | `POST /keys/recovery-email/add` |
| **Auth**     | DID Auth (header)               |

**Request Body:**

```json
{
    "authToken": "firebase-id-token",
    "providerType": "firebase",
    "email": "recovery@example.com"
}
```

**Response (200):**

```json
{ "success": true }
```

***

### Verify Recovery Email

Verify the 6-digit code and persist the recovery email.

|              |                                    |
| ------------ | ---------------------------------- |
| **Endpoint** | `POST /keys/recovery-email/verify` |
| **Auth**     | DID Auth (header)                  |

**Request Body:**

```json
{
    "authToken": "firebase-id-token",
    "providerType": "firebase",
    "code": "123456"
}
```

**Response (200):**

```json
{
    "success": true,
    "maskedRecoveryEmail": "r***@example.com"
}
```

***

### Get Recovery Email

Retrieve the masked recovery email for a user.

|              |                            |
| ------------ | -------------------------- |
| **Endpoint** | `GET /keys/recovery-email` |
| **Auth**     | Query parameters           |

**Query Parameters:**

* `contactMethodType` — `email` or `phone`
* `contactMethodValue` — the email or phone number

**Response (200):**

```json
{
    "maskedRecoveryEmail": "r***@example.com"
}
```

***

### Send Email Backup

Send the encrypted backup share to the user's recovery email (or a specified email).

|              |                           |
| ------------ | ------------------------- |
| **Endpoint** | `POST /keys/email-backup` |
| **Auth**     | Auth Token (body)         |

**Request Body:**

```json
{
    "authToken": "firebase-id-token",
    "providerType": "firebase",
    "encryptedShare": "...",
    "email": "specific@example.com",
    "useRecoveryEmail": false
}
```

Either `email` or `useRecoveryEmail: true` must be provided (not both).

**Response (200):**

```json
{ "success": true }
```

***

### Upgrade Contact Method

Upgrade a user's primary contact method (e.g., phone → email). Verifies an OTP code, links the email to the Firebase account, and updates the server record.

|              |                                     |
| ------------ | ----------------------------------- |
| **Endpoint** | `POST /keys/upgrade-contact-method` |
| **Auth**     | Auth Token (body)                   |

**Request Body:**

```json
{
    "authToken": "firebase-id-token",
    "providerType": "firebase",
    "oldContactMethod": { "type": "phone", "value": "+1234567890" },
    "newContactMethod": { "type": "email", "value": "user@example.com" },
    "code": "123456"
}
```

**Response (200):**

```json
{
    "success": true,
    "customToken": "firebase-custom-token"
}
```

The client should re-authenticate with the returned custom token.

***

### Mark Migrated

Mark a user as migrated from Web3Auth to SSS.

|              |                      |
| ------------ | -------------------- |
| **Endpoint** | `POST /keys/migrate` |
| **Auth**     | Auth Token (body)    |

**Request Body:**

```json
{
    "authToken": "firebase-id-token",
    "providerType": "firebase"
}
```

**Response (200):**

```json
{ "success": true }
```

***

### Delete User Key

Delete all key data for a user. Requires DID-Auth.

|              |                     |
| ------------ | ------------------- |
| **Endpoint** | `POST /keys/delete` |
| **Auth**     | DID Auth (header)   |

**Request Body:**

```json
{
    "authToken": "firebase-id-token",
    "providerType": "firebase"
}
```

**Response (200):**

```json
{ "success": true }
```

***

## QR Login Routes (`/qr-login/*`)

These routes implement the ephemeral relay for [cross-device login](/core-concepts/identities-and-keys/cross-device-login). All session data lives in Redis with short TTLs.

### Create Session

Create a new QR login session with the new device's ephemeral public key.

|              |                          |
| ------------ | ------------------------ |
| **Endpoint** | `POST /qr-login/session` |
| **Auth**     | None                     |

**Request Body:**

```json
{
    "publicKey": "base64-encoded-ephemeral-public-key"
}
```

**Response (200):**

```json
{
    "sessionId": "uuid",
    "shortCode": "123456"
}
```

***

### Look Up Session

Look up a session by its ID or short code.

|              |                                  |
| ------------ | -------------------------------- |
| **Endpoint** | `GET /qr-login/session/{lookup}` |
| **Auth**     | None                             |

**Response (200):**

```json
{
    "sessionId": "uuid",
    "publicKey": "base64-encoded-ephemeral-public-key",
    "status": "pending"
}
```

***

### Approve Session

Post the encrypted device share payload from the logged-in device.

|              |                                              |
| ------------ | -------------------------------------------- |
| **Endpoint** | `POST /qr-login/session/{sessionId}/approve` |
| **Auth**     | Auth Token (body)                            |

**Request Body:**

```json
{
    "authToken": "firebase-id-token",
    "providerType": "firebase",
    "encryptedPayload": "base64-encoded-encrypted-share"
}
```

**Response (200):**

```json
{ "success": true }
```

***

### Send Push Notification

Send a push notification to the user's other devices to prompt approval.

|              |                         |
| ------------ | ----------------------- |
| **Endpoint** | `POST /qr-login/notify` |
| **Auth**     | Auth Token (body)       |

**Request Body:**

```json
{
    "authToken": "firebase-id-token",
    "providerType": "firebase",
    "sessionId": "uuid",
    "shortCode": "123456"
}
```

**Response (200):**

```json
{
    "sent": true,
    "deviceCount": 2
}
```


# Skill Frameworks & OpenSALT

LearnCard Network supports both:

* locally managed skill frameworks (created directly in LearnCard), and
* provider-backed frameworks from OpenSALT (linked by CASE/OpenSALT reference).

This lets you mix custom frameworks with public standards in the same workflow.

## Link an OpenSALT Framework

Use `createSkillFramework` with a framework reference. The reference can be:

* a CASE/OpenSALT framework URL (`/ims/case/v1p0/CFDocuments/...`),
* an OpenSALT URI (`/uri/p...`), or
* a raw framework UUID.

```typescript
const linked = await learnCard.invoke.createSkillFramework({
    frameworkId:
        'https://opensalt.net/ims/case/v1p0/CFDocuments/c6085394-d7cb-11e8-824f-0242ac160002',
});

// linked => { id, name, description, sourceURI, isPublic, ... }
```

After linking, sync skills into LearnCard for local browsing/search:

```typescript
await learnCard.invoke.syncFrameworkSkills({ id: linked.id });
```

## List Frameworks Available to the Current Profile

Use `getAllAvailableFrameworks` to retrieve frameworks the caller can use:

* frameworks they manage, and
* frameworks marked public (including linked OpenSALT frameworks).

```typescript
const page = await learnCard.invoke.getAllAvailableFrameworks({
    limit: 50,
    cursor: null,
    query: { name: { $regex: /math|ela/i } },
});

// page => { records, hasMore, cursor }
```

## Visibility Model

* Managed frameworks default to `isPublic: false`.
* OpenSALT-linked frameworks are stored as public (`isPublic: true`).
* You can set visibility when creating/updating managed frameworks:

```typescript
await learnCard.invoke.createManagedSkillFramework({
    name: 'District Competencies',
    isPublic: true,
});

await learnCard.invoke.updateSkillFramework({
    id: 'framework-id',
    isPublic: false,
});
```

## Use OpenSALT Skills in Boosts

Once linked and synced, OpenSALT skills can be attached to Boosts like any other framework skill:

```typescript
const boostUri = await learnCard.invoke.createBoost(unsignedBoost, {
    skills: [{ frameworkId: linked.id, id: 'skill-id' }],
});

await learnCard.invoke.alignBoostSkills(boostUri, [{ frameworkId: linked.id, id: 'skill-id' }]);
```

When the credential is issued, LearnCard builds OBv3 alignment entries from the framework data so aligned skills are included in VC alignment fields.

## Network Operator Notes

If you run your own LearnCard Network deployment, OpenSALT provider behavior can be configured with:

* `OPENSALT_BASE_URL` (defaults to `https://opensalt.net`)
* `SKILLS_PROVIDER_API_KEY` (optional API key header)


# Architecture

The LearnCloud Network API is deployed as a serverless application on AWS with the following components:

```mermaid
graph TB
    subgraph "AWS Infrastructure"
        api["API Gateway"]
        lambda["Lambda Functions"]
        sg["Security Groups"]
        vpc["Virtual Private Cloud"]

        subgraph "Storage"
            neo4j["Neo4j Database"]
            redis["Redis Cache"]
            sqs["SQS Queue"]
        end
    end

    client["Client Applications"] -->|"HTTP/HTTPS"| api
    api -->|"Invokes"| lambda
    lambda -->|"Query/Update"| neo4j
    lambda -->|"Cache"| redis
    lambda -->|"Send Notifications"| sqs

    sqs -->|"Trigger"| notificationWorker["Notification Worker Lambda"]
    notificationWorker -->|"HTTP"| webhooks["External Webhooks"]
```


# Notifications & Webhooks

The LearnCloud Network includes a notification system that alerts users of important events such as received credentials, connection requests, and consent requests.

## Reading Notifications

### Get Notifications (Paginated)

Fetch a paginated list of notifications with optional filters:

```typescript
const result = await learnCard.invoke.getNotifications(
    { limit: 20, sort: 'REVERSE_CHRONOLOGICAL' },
    { read: false, archived: false } // optional filters
);

console.log(result.notifications); // Array of notifications
console.log(result.hasMore);       // Boolean indicating more pages
console.log(result.cursor);        // Cursor for next page
```

**Options:**

* `limit` - Number of notifications per page
* `cursor` - Pagination cursor from previous response
* `sort` - `'CHRONOLOGICAL'` or `'REVERSE_CHRONOLOGICAL'`

**Filters:**

* `type` - Filter by notification type (e.g., `'CREDENTIAL_RECEIVED'`)
* `read` - Filter by read status (`true` or `false`)
* `archived` - Filter by archived status (`true` or `false`)
* `actionStatus` - Filter by action status (`'PENDING'`, `'COMPLETED'`, `'REJECTED'`)

### Query Notifications

For more flexible querying, use `queryNotifications` to find notifications matching specific criteria:

```typescript
// Find notification by credential URI
const result = await learnCard.invoke.queryNotifications(
    { 'data.vcUris': 'urn:lc:vc:example-credential-uri' },
    { limit: 1 }
);

// Find notifications from a specific sender
const fromSender = await learnCard.invoke.queryNotifications(
    { 'from.did': 'did:web:network.learncard.com:users:sender123' }
);

// Find unread boost notifications
const unreadBoosts = await learnCard.invoke.queryNotifications(
    { type: 'BOOST_RECEIVED', read: false }
);

// Combine multiple filters
const result = await learnCard.invoke.queryNotifications(
    { 
        type: 'CREDENTIAL_RECEIVED',
        archived: false,
        actionStatus: 'PENDING'
    },
    { limit: 10, sort: 'REVERSE_CHRONOLOGICAL' }
);
```

**Query Fields:**

* `type` - Notification type enum
* `from.did` - Sender's DID
* `from.profileId` - Sender's profile ID
* `data.vcUris` - Credential URI(s) associated with the notification
* `data.vpUris` - Presentation URI(s) associated with the notification
* `read` - Read status
* `archived` - Archived status
* `actionStatus` - Action status (`'PENDING'`, `'COMPLETED'`, `'REJECTED'`)

### Update Notification

Mark a notification as read, archived, or update its action status:

```typescript
await learnCard.invoke.updateNotificationMeta(notificationId, {
    read: true,
    actionStatus: 'COMPLETED'
});
```

### Mark All Read

Mark all notifications as read:

```typescript
await learnCard.invoke.markAllNotificationsRead();
```

## Architecture

```mermaid
sequenceDiagram
    participant App as "Client App"
    participant Brain as "LearnCloud Network API"
    participant SQS as "SQS Queue"
    participant Worker as "Lambda Worker"
    participant Webhook as "Notification Webhook"

    App->>Brain: Perform action (e.g., send boost)
    Brain->>SQS: addNotificationToQueue()
    SQS->>Worker: notificationsWorker()
    Worker->>Worker: Parse notification
    Worker->>Webhook: sendNotification() HTTP request
    Note right of Webhook: External notification service
    Webhook-->>Worker: Acknowledge notification
```

## Configuration

Profiles may optionally add a `notificationsWebhook` url, which will cause the LCN service to send payloads of a specific shape to that URL. These payloads are described here.

```typescript
const updatedProfile = {
  notificationsWebhook: 'https://example.com/webhooks/learn-cloud-network',
};

await learnCard.invoke.updateProfile(updatedProfile);
```

## Payload Reference

### Connection Request

When requesting a connection with a profile, the target profile will receive a notification payload of the following structure:

```json
{
    type: 'CONNECTION_REQUEST',
    to: {{ targetProfile }},
    from: {{ requesterProfile }},
    message: {
        title: 'New Connection Request',
        body: '{{ requesterProfile.displayName }} has sent you a connection request!'
    }
}
```

Where `targetProfile` and `requesterProfile` are each `LCNProfile` objects

### Connection Accepted

When accepting a connection request, the profile who requested the connection will receive a notification payload of the following structure:

```json
{
    type: 'CONNECTION_ACCEPTED',
    to: {{ requesterProfile }},
    from: {{ accepterProfile }},
    message: {
        title: 'Connection Accepted',
        body: '{{ accepterProfile.displayName }} has accepted your connection request!'
    }
}
```

Where `accepterProfile` and `requesterProfile` are each `LCNProfile` objects

### Credential Received

When sending a credential to a profile, the profile who will receive the credential will receive a notification payload of the following structure:

```json
{
    type: 'CREDENTIAL_RECEIVED',
    to: {{ receiverProfile }},
    from: {{ senderProfile }},
    message: {
        title: 'Credential Received',
        body: '{{ senderProfile.displayName }} has sent you a credential'
    },
    data: { vcUris: [{{ uri }}] }
}
```

Where `senderProfile` and `receiverProfile` are each `LCNProfile` objects, and `uri` is a [URI](/core-concepts/credentials-and-data/uris) that resolves to the credential that was sent.

### Boost Received

When sending a boost to a profile, the profile who will receive the boost will receive a notification payload of the following structure:

```json
{
    type: 'BOOST_RECEIVED',
    to: {{ receiverProfile }},
    from: {{ senderProfile }},
    message: {
        title: 'Boost Received',
        body: '{{ senderProfile.displayName }} has boosted you!'
    },
    data: { vcUris: [{{ uri }}] }
}
```

Where `senderProfile` and `receiverProfile` are each `LCNProfile` objects, and `uri` is a [URI](/core-concepts/credentials-and-data/uris) that resolves to the boost that was sent.

### Boost Accepted

When accepting a sent boost, the profile who sent the boost will receive a notification payload of the following structure:

```json
{
    type: 'BOOST_ACCEPTED',
    to: {{ senderProfile }},
    from: {{ receiverProfile }},
    message: {
        title: 'Boost Accepted',
        body: '{{ receiverProfile.displayName }} has accepted your boost'
    },
    data: { vcUris: [{{ uri }}] }
}
```

Where `senderProfile` and `receiverProfile` are each `LCNProfile` objects, and `uri` is a [URI](/core-concepts/credentials-and-data/uris) that resolves to the boost that was sent.

### Presentation Received

When sending a presentation to a profile, the profile who will receive the presentation will receive a notification payload of the following structure:

```json
{
    type: 'PRESENTATION_RECEIVED',
    to: {{ receiverProfile }},
    from: {{ senderProfile }},
    message: {
        title: 'Presentation Received',
        body: '{{ senderProfile.displayName }} has sent you a presentation'
    },
    data: { vpUris: [{{ uri }}] }
}
```

Where `senderProfile` and `receiverProfile` are each `LCNProfile` objects, and `uri` is a [URI](/core-concepts/credentials-and-data/uris) that resolves to the presentation that was sent.

### Consent Flow Transaction

#### Consent

When consenting to a Consent Flow Contract, the owner of the contract will receive a notification payload of the following structure:

```json
{
    type: 'CONSENT_FLOW_TRANSACTION',
    to: {{ ownerProfile }},
    from: {{ consenterProfile }},
    message: {
        title: 'New Consent Transaction',
        body: '{{ consenterProfile.displayName }} has just consented to {{ contract.name }}!'
    },
    data: { transaction: {{ transaction }} }
}
```

Where `ownerProfile` and `consenterProfile` are each `LCNProfile` objects, `contract` is a `ConsentFlowContractDetails` object, and `transaction` is a `ConsentFlowTransaction` object with its `action` set to `'consent'`.

#### Re-Consent

When a profile consents, withdraws consent, then *reconsents* to a contract, the owner of the contract will receive a notification payload of the following structure:

```json
{
    type: 'CONSENT_FLOW_TRANSACTION',
    to: {{ ownerProfile }},
    from: {{ consenterProfile }},
    message: {
        title: 'New Consent Transaction',
        body: '{{ consenterProfile.displayName }} has just reconsented to {{ contract.name }}!'
    },
    data: { transaction: {{ transaction }} }
}
```

Where `ownerProfile` and `consenterProfile` are each `LCNProfile` objects, `contract` is a `ConsentFlowContractDetails` object, and `transaction` is a `ConsentFlowTransaction` object with its `action` set to `'consent'`.

#### Updated Terms

When a profile updates its terms to a contract, the owner of the contract will receive a notification payload of the following structure:

```json
{
    type: 'CONSENT_FLOW_TRANSACTION',
    to: {{ ownerProfile }},
    from: {{ consenterProfile }},
    message: {
        title: 'New Consent Transaction',
        body: '{{ consenterProfile.displayName }} has just updated their terms to {{ contract.name }}!'
    },
    data: { transaction: {{ transaction }} }
}
```

Where `ownerProfile` and `consenterProfile` are each `LCNProfile` objects, `contract` is a `ConsentFlowContractDetails` object, and `transaction` is a `ConsentFlowTransaction` object with its `action` set to `'update'`.

#### Withdrawn Consent

When a profile withdraws consent to a contract, the owner of the contrct will receive a notification payload of the following structure:

```json
{
    type: 'CONSENT_FLOW_TRANSACTION',
    to: {{ ownerProfile }},
    from: {{ consenterProfile }},
    message: {
        title: 'New Consent Transaction',
        body: '{{ consenterProfile.displayName }} has just withdrawn their terms to {{ contract.name }}!'
    },
    data: { transaction: {{ transaction }} }
}
```

Where `ownerProfile` and `consenterProfile` are each `LCNProfile` objects, `contract` is a `ConsentFlowContractDetails` object, and `transaction` is a `ConsentFlowTransaction` object with its `action` set to `'withdraw'`.


# Profiles

## Create a profile

> Creates a profile for a user

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/create":{"post":{"operationId":"profile-createProfile","summary":"Create a profile","description":"Creates a profile for a user","tags":["Profiles"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}}},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"},"authToken":{"type":"string"}},"required":["profileId"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Create a service profile

> Creates a service profile

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/create-service":{"post":{"operationId":"profile-createServiceProfile","summary":"Create a service profile","description":"Creates a service profile","tags":["Profiles"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}}},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Create a managed service profile

> Creates a managed service profile

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/create-managed-service":{"post":{"operationId":"profile-createManagedServiceProfile","summary":"Create a managed service profile","description":"Creates a managed service profile","tags":["Profiles"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}}},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get your profile information

> This route uses the request header to grab the profile of the current user

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile":{"get":{"operationId":"profile-getProfile","summary":"Get your profile information","description":"This route uses the request header to grab the profile of the current user","tags":["Profiles"],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Update your profile

> This route updates the profile of the current user

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile":{"post":{"operationId":"profile-updateProfile","summary":"Update your profile","description":"This route updates the profile of the current user","tags":["Profiles"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"profileId":{"type":"string"},"displayName":{"type":"string"},"shortBio":{"type":"string"},"bio":{"type":"string"},"isPrivate":{"type":"boolean"},"profileVisibility":{"type":"string","enum":["public","connections_only","private"]},"showEmail":{"type":"boolean"},"allowConnectionRequests":{"type":"string","enum":["anyone","invite_only"]},"image":{"type":"string"},"heroImage":{"type":"string"},"websiteLink":{"type":"string"},"type":{"type":"string"},"email":{"type":"string"},"notificationsWebhook":{"type":"string"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}}},"role":{"type":"string"},"dob":{"type":"string"},"country":{"type":"string"},"locale":{"type":"string"},"highlightedCredentials":{"type":"array","items":{"type":"string"}},"approved":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Delete your profile

> This route deletes the profile of the current user

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile":{"delete":{"operationId":"profile-deleteProfile","summary":"Delete your profile","description":"This route deletes the profile of the current user","tags":["Profiles"],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get profile information

> This route grabs the profile information of any user, using their profileId

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/{profileId}":{"get":{"operationId":"profile-getOtherProfile","summary":"Get profile information","description":"This route grabs the profile information of any user, using their profileId","tags":["Profiles"],"parameters":[{"in":"path","name":"profileId","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false}},"required":["profileId","displayName","shortBio"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}]}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Available Profiles

> This route gets all of your available profiles. That is, profiles you directly or indirectly manage

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/available-profiles":{"post":{"operationId":"profile-getAvailableProfiles","summary":"Available Profiles","description":"This route gets all of your available profiles. That is, profiles you directly or indirectly manage","tags":["Profiles"],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"sort":{"type":"string"},"query":{"type":"object","properties":{"profileId":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"displayName":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"shortBio":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"bio":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"email":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"websiteLink":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"isServiceProfile":{"type":"boolean"},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}}}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"profile":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},"manager":{"type":"object","properties":{"id":{"type":"string"},"created":{"type":"string"},"displayName":{"default":"","type":"string"},"shortBio":{"default":"","type":"string"},"bio":{"default":"","type":"string"},"email":{"type":"string"},"image":{"type":"string"},"heroImage":{"type":"string"},"did":{"type":"string"}},"required":["id","created","did"],"additionalProperties":false}},"required":["profile"],"additionalProperties":false}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Managed Service Profiles

> This route gets all of your managed service profiles

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/managed-services":{"get":{"operationId":"profile-getManagedServiceProfiles","summary":"Managed Service Profiles","description":"This route gets all of your managed service profiles","tags":["Profiles"],"parameters":[{"in":"query","name":"limit","schema":{"default":25,"type":"number"}},{"in":"query","name":"cursor","schema":{"type":"string"}},{"in":"query","name":"sort","schema":{"type":"string"}},{"in":"query","name":"id","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Search profiles

> This route searches for profiles based on their profileId

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/search/profiles/{input}":{"get":{"operationId":"profile-searchProfiles","summary":"Search profiles","description":"This route searches for profiles based on their profileId","tags":["Profiles"],"parameters":[{"in":"path","name":"input","schema":{"type":"string"},"required":true},{"in":"query","name":"limit","schema":{"default":25,"type":"integer","exclusiveMinimum":0,"exclusiveMaximum":100}},{"in":"query","name":"includeSelf","schema":{"default":false,"type":"boolean"}},{"in":"query","name":"includeConnectionStatus","schema":{"default":false,"type":"boolean"}},{"in":"query","name":"includeServiceProfiles","schema":{"default":false,"type":"boolean"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"allOf":[{"type":"object","properties":{"connectionStatus":{"type":"string","enum":["CONNECTED","PENDING_REQUEST_SENT","PENDING_REQUEST_RECEIVED","NOT_CONNECTED"]}},"additionalProperties":false},{"anyOf":[{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false}},"required":["profileId","displayName","shortBio"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}]}]}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Connect with another profile

> This route uses the request header to send a connection request to another user based on their profileId

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/{profileId}/connect":{"post":{"operationId":"profile-connectWith","summary":"Connect with another profile","description":"This route uses the request header to send a connection request to another user based on their profileId","tags":["Profiles"],"parameters":[{"in":"path","name":"profileId","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Connect with another profile (expired invite)

> Send a connection request triggered from an expired invite link

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/{profileId}/connect-expired-invite":{"post":{"operationId":"profile-connectWithExpiredInvite","summary":"Connect with another profile (expired invite)","description":"Send a connection request triggered from an expired invite link","tags":["Profiles"],"parameters":[{"in":"path","name":"profileId","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Cancel Connection Request

> Cancels connection request with another profile

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/{profileId}/cancel-connection-request":{"post":{"operationId":"profile-cancelConnectionRequest","summary":"Cancel Connection Request","description":"Cancels connection request with another profile","tags":["Profiles"],"parameters":[{"in":"path","name":"profileId","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## POST /profile/{profileId}/connect/{challenge}

> Connect using an invitation

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/{profileId}/connect/{challenge}":{"post":{"operationId":"profile-connectWithInvite","summary":"Connect using an invitation","tags":["Profiles"],"parameters":[{"in":"path","name":"profileId","schema":{"type":"string"},"required":true},{"in":"path","name":"challenge","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Disconnect with another profile

> This route uses the request header to disconnect with another user based on their profileId

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/{profileId}/disconnect":{"post":{"operationId":"profile-disconnectWith","summary":"Disconnect with another profile","description":"This route uses the request header to disconnect with another user based on their profileId","tags":["Profiles"],"parameters":[{"in":"path","name":"profileId","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Accept Connection Request

> This route uses the request header to accept a connection request from another user based on their profileId

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/{profileId}/accept-connection":{"post":{"operationId":"profile-acceptConnectionRequest","summary":"Accept Connection Request","description":"This route uses the request header to accept a connection request from another user based on their profileId","tags":["Profiles"],"parameters":[{"in":"path","name":"profileId","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## View connections

> This route shows the current user's connections.\
> Warning! This route is deprecated and currently has a hard limit of returning only the first 50 connections. Please use paginatedConnections instead!

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/connections":{"get":{"operationId":"profile-connections","summary":"View connections","description":"This route shows the current user's connections.\nWarning! This route is deprecated and currently has a hard limit of returning only the first 50 connections. Please use paginatedConnections instead!","tags":["Profiles"],"deprecated":true,"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"anyOf":[{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false}},"required":["profileId","displayName","shortBio"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}]}}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## View connections

> This route shows the current user's connections

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/connections/paginated":{"get":{"operationId":"profile-paginatedConnections","summary":"View connections","description":"This route shows the current user's connections","tags":["Profiles"],"parameters":[{"in":"query","name":"limit","schema":{"default":25,"type":"number"}},{"in":"query","name":"cursor","schema":{"type":"string"}},{"in":"query","name":"sort","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"anyOf":[{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false}},"required":["profileId","displayName","shortBio"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}]}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## View pending connections

> This route shows the current user's pending connections.\
> Warning! This route is deprecated and currently has a hard limit of returning only the first 50 connections. Please use paginatedPendingConnections instead

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/pending-connections":{"get":{"operationId":"profile-pendingConnections","summary":"View pending connections","description":"This route shows the current user's pending connections.\nWarning! This route is deprecated and currently has a hard limit of returning only the first 50 connections. Please use paginatedPendingConnections instead","tags":["Profiles"],"deprecated":true,"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"anyOf":[{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false}},"required":["profileId","displayName","shortBio"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}]}}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## View pending connections

> This route shows the current user's pending connections

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/pending-connections/paginated":{"get":{"operationId":"profile-paginatedPendingConnections","summary":"View pending connections","description":"This route shows the current user's pending connections","tags":["Profiles"],"parameters":[{"in":"query","name":"limit","schema":{"default":25,"type":"number"}},{"in":"query","name":"cursor","schema":{"type":"string"}},{"in":"query","name":"sort","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"anyOf":[{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false}},"required":["profileId","displayName","shortBio"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}]}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## View connection requests

> This route shows the current user's connection requests.\
> Warning! This route is deprecated and currently has a hard limit of returning only the first 50 connections. Please use paginatedConnectionRequests instead

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/connection-requests":{"get":{"operationId":"profile-connectionRequests","summary":"View connection requests","description":"This route shows the current user's connection requests.\nWarning! This route is deprecated and currently has a hard limit of returning only the first 50 connections. Please use paginatedConnectionRequests instead","tags":["Profiles"],"deprecated":true,"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"anyOf":[{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false}},"required":["profileId","displayName","shortBio"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}]}}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## View connection requests

> This route shows the current user's connection requests

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/connection-requests/paginated":{"get":{"operationId":"profile-paginatedConnectionRequests","summary":"View connection requests","description":"This route shows the current user's connection requests","tags":["Profiles"],"parameters":[{"in":"query","name":"limit","schema":{"default":25,"type":"number"}},{"in":"query","name":"cursor","schema":{"type":"string"}},{"in":"query","name":"sort","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"anyOf":[{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false}},"required":["profileId","displayName","shortBio"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}]}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Generate a connection invitation

> Generate a connection invitation challenge. By default, invites are single-use; set maxUses > 1 for multi-use, or maxUses = 0 for unlimited. Expiration is in seconds (default 30 days); set expiration = 0 for no expiration.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/generate-invite":{"post":{"operationId":"profile-generateInvite","summary":"Generate a connection invitation","description":"Generate a connection invitation challenge. By default, invites are single-use; set maxUses > 1 for multi-use, or maxUses = 0 for unlimited. Expiration is in seconds (default 30 days); set expiration = 0 for no expiration.","tags":["Profiles"],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"expiration":{"default":2592000,"type":"number"},"challenge":{"type":"string"},"maxUses":{"default":1,"type":"integer","minimum":0,"maximum":9007199254740991}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"profileId":{"type":"string"},"challenge":{"type":"string"},"expiresIn":{"anyOf":[{"type":"number"},{"type":"null"}]}},"required":["profileId","challenge","expiresIn"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## List valid connection invitations

> List all valid connection invitation links you've created. Each item includes: challenge, expiresIn (seconds or null), usesRemaining (number or null), and maxUses (number or null). Exhausted invites are omitted.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/invites":{"get":{"operationId":"profile-listInvites","summary":"List valid connection invitations","description":"List all valid connection invitation links you've created. Each item includes: challenge, expiresIn (seconds or null), usesRemaining (number or null), and maxUses (number or null). Exhausted invites are omitted.","tags":["Profiles"],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"challenge":{"type":"string"},"expiresIn":{"anyOf":[{"type":"number"},{"type":"null"}]},"usesRemaining":{"anyOf":[{"type":"number"},{"type":"null"}]},"maxUses":{"anyOf":[{"type":"number"},{"type":"null"}]}},"required":["challenge","expiresIn","usesRemaining","maxUses"],"additionalProperties":false}}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Invalidate an invitation

> Invalidate a specific connection invitation by its challenge string. Idempotent: returns true even if the invite was already invalid or missing.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/invite/{challenge}/invalidate":{"post":{"operationId":"profile-invalidateInvite","summary":"Invalidate an invitation","description":"Invalidate a specific connection invitation by its challenge string. Idempotent: returns true even if the invite was already invalid or missing.","tags":["Profiles"],"parameters":[{"in":"path","name":"challenge","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Block another profile

> Block another user based on their profileId

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/{profileId}/block":{"post":{"operationId":"profile-blockProfile","summary":"Block another profile","description":"Block another user based on their profileId","tags":["Profiles"],"parameters":[{"in":"path","name":"profileId","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Unblock another profile

> Unblock another user based on their profileId

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/{profileId}/unblock":{"post":{"operationId":"profile-unblockProfile","summary":"Unblock another profile","description":"Unblock another user based on their profileId","tags":["Profiles"],"parameters":[{"in":"path","name":"profileId","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## View blocked profiles

> This route shows the current user's blocked profiles

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/blocked":{"get":{"operationId":"profile-blocked","summary":"View blocked profiles","description":"This route shows the current user's blocked profiles","tags":["Profiles"],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"anyOf":[{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false}},"required":["profileId","displayName","shortBio"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}]}}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Register a Signing Authority

> This route is used to register a signing authority that can sign credentials on the current user's behalf

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/signing-authority/register":{"post":{"operationId":"profile-registerSigningAuthority","summary":"Register a Signing Authority","description":"This route is used to register a signing authority that can sign credentials on the current user's behalf","tags":["Profiles"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"endpoint":{"type":"string"},"name":{"type":"string","maxLength":15,"pattern":"^[a-z0-9-]+$"},"did":{"type":"string"}},"required":["endpoint","name","did"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get Signing Authorities for user

> This route is used to get registered signing authorities that can sign credentials on the current user's behalf

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/signing-authority/get/all":{"get":{"operationId":"profile-signingAuthorities","summary":"Get Signing Authorities for user","description":"This route is used to get registered signing authorities that can sign credentials on the current user's behalf","tags":["Profiles"],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"signingAuthority":{"type":"object","properties":{"endpoint":{"type":"string"}},"required":["endpoint"],"additionalProperties":false},"relationship":{"type":"object","properties":{"name":{"type":"string","maxLength":15,"pattern":"^[a-z0-9-]+$"},"did":{"type":"string"},"isPrimary":{"type":"boolean"}},"required":["name","did"],"additionalProperties":false}},"required":["signingAuthority","relationship"],"additionalProperties":false}}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get Signing Authority for user

> This route is used to get a named signing authority that can sign credentials on the current user's behalf

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/signing-authority/get":{"get":{"operationId":"profile-signingAuthority","summary":"Get Signing Authority for user","description":"This route is used to get a named signing authority that can sign credentials on the current user's behalf","tags":["Profiles"],"parameters":[{"in":"query","name":"endpoint","schema":{"type":"string"},"required":true},{"in":"query","name":"name","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"signingAuthority":{"type":"object","properties":{"endpoint":{"type":"string"}},"required":["endpoint"],"additionalProperties":false},"relationship":{"type":"object","properties":{"name":{"type":"string","maxLength":15,"pattern":"^[a-z0-9-]+$"},"did":{"type":"string"},"isPrimary":{"type":"boolean"}},"required":["name","did"],"additionalProperties":false}},"required":["signingAuthority","relationship"],"additionalProperties":false},{"not":{}}]}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Set Primary Signing Authority

> This route is used to set a signing authority as the primary one for the current user

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/signing-authority/set-primary":{"post":{"operationId":"profile-setPrimarySigningAuthority","summary":"Set Primary Signing Authority","description":"This route is used to set a signing authority as the primary one for the current user","tags":["Profiles"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"endpoint":{"type":"string"},"name":{"type":"string","maxLength":15,"pattern":"^[a-z0-9-]+$"}},"required":["endpoint","name"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get primary Signing Authority for user

> This route is used to get the primary signing authority that can sign credentials on the current user's behalf

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/signing-authority/get-primary":{"get":{"operationId":"profile-primarySigningAuthority","summary":"Get primary Signing Authority for user","description":"This route is used to get the primary signing authority that can sign credentials on the current user's behalf","tags":["Profiles"],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"signingAuthority":{"type":"object","properties":{"endpoint":{"type":"string"}},"required":["endpoint"],"additionalProperties":false},"relationship":{"type":"object","properties":{"name":{"type":"string","maxLength":15,"pattern":"^[a-z0-9-]+$"},"did":{"type":"string"},"isPrimary":{"type":"boolean"}},"required":["name","did"],"additionalProperties":false}},"required":["signingAuthority","relationship"],"additionalProperties":false},{"not":{}}]}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Create a managed profile

> Creates a managed profile

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"},{"name":"Profile Managers"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/create-managed-profile":{"post":{"operationId":"profileManager-createManagedProfile","summary":"Create a managed profile","description":"Creates a managed profile","tags":["Profiles","Profile Managers"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}}},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Managed Profiles

> This route gets all of your managed profiles

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"},{"name":"Profile Managers"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/managed-profiles":{"post":{"operationId":"profileManager-getManagedProfiles","summary":"Managed Profiles","description":"This route gets all of your managed profiles","tags":["Profiles","Profile Managers"],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"sort":{"type":"string"},"query":{"type":"object","properties":{"profileId":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"displayName":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"shortBio":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"bio":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"email":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"websiteLink":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"isServiceProfile":{"type":"boolean"},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}}}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Send Guardian Approval Email

> Generates a one-time approval token and emails a link to the guardian. When the link is consumed, the requester's profile will be marked as approved.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/inbox/guardian-approval/send":{"post":{"operationId":"inbox-sendGuardianApprovalEmail","summary":"Send Guardian Approval Email","description":"Generates a one-time approval token and emails a link to the guardian. When the link is consumed, the requester's profile will be marked as approved.","tags":["Universal Inbox","Profiles"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"guardianEmail":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"ttlHours":{"type":"integer","minimum":0,"maximum":720},"template":{"type":"object","properties":{"id":{"type":"string"},"model":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}}}},"required":["guardianEmail"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"approvalUrl":{"type":"string"}},"required":["message","approvalUrl"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Approve Guardian Request

> Consumes a guardian approval token and marks the requesting user profile as approved.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/inbox/guardian-approval/approve":{"post":{"operationId":"inbox-approveGuardianRequest","summary":"Approve Guardian Request","description":"Consumes a guardian approval token and marks the requesting user profile as approved.","tags":["Universal Inbox","Profiles"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"}},"required":["token"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Approve Guardian Request (GET)

> GET endpoint to consume guardian approval token from URL path.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profiles"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/inbox/guardian-approval/{token}":{"get":{"operationId":"inbox-approveGuardianRequestByPath","summary":"Approve Guardian Request (GET)","description":"GET endpoint to consume guardian approval token from URL path.","tags":["Universal Inbox","Profiles"],"parameters":[{"in":"path","name":"token","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```


# Profile Managers

## Create a profile manager

> Creates a profile manager

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profile Managers"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile-manager/create":{"post":{"operationId":"profileManager-createProfileManager","summary":"Create a profile manager","description":"Creates a profile manager","tags":["Profile Managers"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"displayName":{"default":"","type":"string"},"shortBio":{"default":"","type":"string"},"bio":{"default":"","type":"string"},"email":{"type":"string"},"image":{"type":"string"},"heroImage":{"type":"string"},"managerType":{"type":"string"}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Create a profile manager that is a child of a Boost

> Creates a profile manager that is a child of a Boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profile Managers"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile-manager/create-child":{"post":{"operationId":"profileManager-createChildProfileManager","summary":"Create a profile manager that is a child of a Boost","description":"Creates a profile manager that is a child of a Boost","tags":["Profile Managers"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"parentUri":{"type":"string"},"profile":{"type":"object","properties":{"displayName":{"default":"","type":"string"},"shortBio":{"default":"","type":"string"},"bio":{"default":"","type":"string"},"email":{"type":"string"},"image":{"type":"string"},"heroImage":{"type":"string"},"managerType":{"type":"string"}}}},"required":["parentUri","profile"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get profiles managed by the current user

> Returns all profiles managed via ProfileManagers that the current profile administrates.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profile Managers"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile-manager/my-managed-children":{"get":{"operationId":"profileManager-getMyManagedChildren","summary":"Get profiles managed by the current user","description":"Returns all profiles managed via ProfileManagers that the current profile administrates.","tags":["Profile Managers"],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get profiles that manage the current user

> Returns all profiles that manage the current profile via a MANAGES relationship.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profile Managers"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile-manager/my-guardians":{"get":{"operationId":"profileManager-getMyGuardians","summary":"Get profiles that manage the current user","description":"Returns all profiles that manage the current profile via a MANAGES relationship.","tags":["Profile Managers"],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Remove a MANAGES relationship

> Removes the MANAGES relationship between the current profile and a child profile, or between a guardian and the current profile.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profile Managers"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile-manager/remove-manages":{"post":{"operationId":"profileManager-removeManagesRelationship","summary":"Remove a MANAGES relationship","description":"Removes the MANAGES relationship between the current profile and a child profile, or between a guardian and the current profile.","tags":["Profile Managers"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"profileId":{"type":"string"}},"required":["profileId"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get your profile manager profile information

> This route uses the request header to grab the profile manager profile of the current profile manager

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profile Managers"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile-manager":{"get":{"operationId":"profileManager-getProfileManager","summary":"Get your profile manager profile information","description":"This route uses the request header to grab the profile manager profile of the current profile manager","tags":["Profile Managers"],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"created":{"type":"string"},"displayName":{"default":"","type":"string"},"shortBio":{"default":"","type":"string"},"bio":{"default":"","type":"string"},"email":{"type":"string"},"image":{"type":"string"},"heroImage":{"type":"string"},"did":{"type":"string"}},"required":["id","created","did"],"additionalProperties":false}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Update the profile of your Profile Manager

> This route updates the profile of the current profile manager

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profile Managers"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile-manager":{"post":{"operationId":"profileManager-updateProfileManager","summary":"Update the profile of your Profile Manager","description":"This route updates the profile of the current profile manager","tags":["Profile Managers"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"displayName":{"default":"","type":"string"},"shortBio":{"default":"","type":"string"},"bio":{"default":"","type":"string"},"email":{"type":"string"},"image":{"type":"string"},"heroImage":{"type":"string"}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get profile manager information

> This route grabs the profile information of any profile manager, using their id

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profile Managers"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile-manager/{id}":{"get":{"operationId":"profileManager-getOtherProfileManager","summary":"Get profile manager information","description":"This route grabs the profile information of any profile manager, using their id","tags":["Profile Managers"],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"created":{"type":"string"},"displayName":{"default":"","type":"string"},"shortBio":{"default":"","type":"string"},"bio":{"default":"","type":"string"},"email":{"type":"string"},"image":{"type":"string"},"heroImage":{"type":"string"},"did":{"type":"string"}},"required":["id","created","did"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```


# Credentials

## Send a Credential

> This endpoint sends a credential to a user based on their profileId

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Credentials"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/credential/send/{profileId}":{"post":{"operationId":"credential-sendCredential","summary":"Send a Credential","description":"This endpoint sends a credential to a user based on their profileId","tags":["Credentials"],"parameters":[{"in":"path","name":"profileId","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"credential":{"anyOf":[{"anyOf":[{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]}},"required":["@context","type","issuer","credentialSubject"],"additionalProperties":{}},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}}]},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]}]},"metadata":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["credential"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Accept a Credential

> This endpoint accepts a credential

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Credentials"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/credential/accept":{"post":{"operationId":"credential-acceptCredential","summary":"Accept a Credential","description":"This endpoint accepts a credential","tags":["Credentials"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uri":{"type":"string"},"options":{"type":"object","properties":{"skipNotification":{"default":false,"type":"boolean"},"metadata":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}}}},"required":["uri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get received credentials

> This endpoint returns the current user's received credentials

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Credentials"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/credentials/received":{"get":{"operationId":"credential-receivedCredentials","summary":"Get received credentials","description":"This endpoint returns the current user's received credentials","tags":["Credentials"],"parameters":[{"in":"query","name":"limit","schema":{"default":25,"type":"integer","exclusiveMinimum":0,"exclusiveMaximum":100}},{"in":"query","name":"from","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"uri":{"type":"string"},"to":{"type":"string"},"from":{"type":"string"},"sent":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"received":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"metadata":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["uri","to","from","sent"],"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get sent credentials

> This endpoint returns the current user's sent credentials

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Credentials"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/credentials/sent":{"get":{"operationId":"credential-sentCredentials","summary":"Get sent credentials","description":"This endpoint returns the current user's sent credentials","tags":["Credentials"],"parameters":[{"in":"query","name":"limit","schema":{"default":25,"type":"integer","exclusiveMinimum":0,"exclusiveMaximum":100}},{"in":"query","name":"to","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"uri":{"type":"string"},"to":{"type":"string"},"from":{"type":"string"},"sent":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"received":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"metadata":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["uri","to","from","sent"],"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get incoming credentials

> This endpoint returns the current user's incoming credentials

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Credentials"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/credentials/incoming":{"get":{"operationId":"credential-incomingCredentials","summary":"Get incoming credentials","description":"This endpoint returns the current user's incoming credentials","tags":["Credentials"],"parameters":[{"in":"query","name":"limit","schema":{"default":25,"type":"integer","exclusiveMinimum":0,"exclusiveMaximum":100}},{"in":"query","name":"from","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"uri":{"type":"string"},"to":{"type":"string"},"from":{"type":"string"},"sent":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"received":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"metadata":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["uri","to","from","sent"],"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Delete a credential

> This endpoint deletes a credential

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Credentials"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/credential":{"delete":{"operationId":"credential-deleteCredential","summary":"Delete a credential","description":"This endpoint deletes a credential","tags":["Credentials"],"parameters":[{"in":"query","name":"uri","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get revoked credentials

> This endpoint returns credential URIs that have been revoked for the current user

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Credentials"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/credentials/revoked":{"get":{"operationId":"credential-getRevokedCredentials","summary":"Get revoked credentials","description":"This endpoint returns credential URIs that have been revoked for the current user","tags":["Credentials"],"parameters":[],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get holder export metadata

> Returns holder-owned continuity metadata such as consent records and transaction history

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Credentials"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/holder-export/metadata":{"get":{"operationId":"credential-getHolderExportMetadata","summary":"Get holder export metadata","description":"Returns holder-owned continuity metadata such as consent records and transaction history","tags":["Credentials"],"parameters":[],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"consentRecords":{"type":"array","items":{"type":"object","properties":{"termsUri":{"type":"string"},"status":{"type":"string","enum":["live","stale","withdrawn"]},"contract":{"type":"object","properties":{"contract":{"type":"object","properties":{"read":{"type":"object","properties":{"anonymize":{"type":"boolean"},"credentials":{"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["credentials","personal"],"additionalProperties":false},"write":{"type":"object","properties":{"credentials":{"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["credentials","personal"],"additionalProperties":false}},"required":["read","write"],"additionalProperties":false},"owner":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},"name":{"type":"string"},"subtitle":{"type":"string"},"description":{"type":"string"},"reasonForAccessing":{"type":"string"},"image":{"type":"string"},"uri":{"type":"string"},"needsGuardianConsent":{"type":"boolean"},"redirectUrl":{"type":"string"},"frontDoorBoostUri":{"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"expiresAt":{"type":"string"},"autoBoosts":{"type":"array","items":{"type":"string"}},"writers":{"type":"array","items":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}}},"required":["contract","owner","name","uri","createdAt","updatedAt"],"additionalProperties":false},"terms":{"type":"object","properties":{"read":{"type":"object","properties":{"anonymize":{"type":"boolean"},"credentials":{"type":"object","properties":{"shareAll":{"type":"boolean"},"sharing":{"type":"boolean"},"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"sharing":{"type":"boolean"},"shared":{"type":"array","items":{"type":"string"}},"shareAll":{"type":"boolean"},"shareUntil":{"type":"string"}},"additionalProperties":false}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}}},"required":["credentials","personal"],"additionalProperties":false},"write":{"type":"object","properties":{"credentials":{"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}},"required":["credentials","personal"],"additionalProperties":false},"deniedWriters":{"type":"array","items":{"type":"string"}}},"required":["read","write"],"additionalProperties":false},"transactions":{"type":"array","items":{"type":"object","properties":{"expiresAt":{"type":"string"},"oneTime":{"type":"boolean"},"terms":{"type":"object","properties":{"read":{"type":"object","properties":{"anonymize":{"type":"boolean"},"credentials":{"type":"object","properties":{"shareAll":{"type":"boolean"},"sharing":{"type":"boolean"},"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"sharing":{"type":"boolean"},"shared":{"type":"array","items":{"type":"string"}},"shareAll":{"type":"boolean"},"shareUntil":{"type":"string"}},"additionalProperties":false}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}}},"required":["credentials","personal"],"additionalProperties":false},"write":{"type":"object","properties":{"credentials":{"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}},"required":["credentials","personal"],"additionalProperties":false},"deniedWriters":{"type":"array","items":{"type":"string"}}},"required":["read","write"],"additionalProperties":false},"id":{"type":"string"},"action":{"type":"string","enum":["consent","update","sync","withdraw","write"]},"date":{"type":"string"},"uris":{"type":"array","items":{"type":"string"}}},"required":["id","action","date"],"additionalProperties":false}}},"required":["termsUri","status","contract","terms","transactions"],"additionalProperties":false}},"truncated":{"type":"boolean"},"warnings":{"type":"array","items":{"type":"string"}},"limits":{"type":"object","properties":{"maxConsentRecords":{"type":"number"},"maxTransactionsPerConsentRecord":{"type":"number"}},"required":["maxConsentRecords","maxTransactionsPerConsentRecord"],"additionalProperties":false}},"required":["consentRecords"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```


# Boosts

## Get OBv3 alignments for a boost

> Returns OBv3 alignment entries based on the boost's linked framework and aligned skills. Requires issue permission.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/alignments":{"get":{"operationId":"boost-getBoostAlignments","summary":"Get OBv3 alignments for a boost","description":"Returns OBv3 alignment entries based on the boost's linked framework and aligned skills. Requires issue permission.","tags":["Boosts"],"parameters":[{"in":"query","name":"uri","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"targetCode":{"type":"string"},"targetName":{"type":"string"},"targetDescription":{"type":"string"},"targetUrl":{"type":"string"},"targetFramework":{"type":"string"}},"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get aligned skills for a boost

> Returns skills aligned to a boost via ALIGNED\_TO, including proficiencyLevel stored on the relationship.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/skills":{"get":{"operationId":"boost-getBoostSkills","summary":"Get aligned skills for a boost","description":"Returns skills aligned to a boost via ALIGNED_TO, including proficiencyLevel stored on the relationship.","tags":["Boosts"],"parameters":[{"in":"query","name":"uri","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"statement":{"type":"string"},"description":{"type":"string"},"code":{"type":"string"},"icon":{"type":"string"},"type":{"default":"skill","type":"string"},"status":{"default":"active","type":"string","enum":["active","archived"]},"frameworkId":{"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"proficiencyLevel":{"type":"number"}},"required":["id","statement","type","status"],"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Attach framework to boost

> Ensures a USES\_FRAMEWORK relationship from a boost to a SkillFramework. Requires boost admin.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/attach-framework":{"post":{"operationId":"boost-attachFrameworkToBoost","summary":"Attach framework to boost","description":"Ensures a USES_FRAMEWORK relationship from a boost to a SkillFramework. Requires boost admin.","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"boostUri":{"type":"string"},"frameworkId":{"type":"string"}},"required":["boostUri","frameworkId"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Detach framework from boost

> Removes a USES\_FRAMEWORK relationship from a boost to a SkillFramework. Requires boost admin.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/detach-framework":{"post":{"operationId":"boost-detachFrameworkFromBoost","summary":"Detach framework from boost","description":"Removes a USES_FRAMEWORK relationship from a boost to a SkillFramework. Requires boost admin.","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"boostUri":{"type":"string"},"frameworkId":{"type":"string"}},"required":["boostUri","frameworkId"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Align skills to boost

> Ensures ALIGNED\_TO relationships from a boost to Skill nodes. Requires boost admin.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/align-skills":{"post":{"operationId":"boost-alignBoostSkills","summary":"Align skills to boost","description":"Ensures ALIGNED_TO relationships from a boost to Skill nodes. Requires boost admin.","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"boostUri":{"type":"string"},"skills":{"minItems":1,"type":"array","items":{"type":"object","properties":{"frameworkId":{"type":"string"},"id":{"type":"string"},"proficiencyLevel":{"type":"number"}},"required":["frameworkId","id"]}}},"required":["boostUri","skills"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## List available skills for a boost

> Returns skills from frameworks attached to the boost or any of its ancestors. Requires boost admin.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/skills/available":{"get":{"operationId":"boost-getSkillsAvailableForBoost","summary":"List available skills for a boost","description":"Returns skills from frameworks attached to the boost or any of its ancestors. Requires boost admin.","tags":["Boosts"],"parameters":[{"in":"query","name":"uri","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"framework":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"image":{"type":"string"},"sourceURI":{"type":"string","format":"uri"},"isPublic":{"default":false,"type":"boolean"},"status":{"default":"active","type":"string","enum":["active","archived"]},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","name","isPublic","status"],"additionalProperties":false},"skills":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"statement":{"type":"string"},"description":{"type":"string"},"code":{"type":"string"},"icon":{"type":"string"},"type":{"default":"skill","type":"string"},"status":{"default":"active","type":"string","enum":["active","archived"]},"frameworkId":{"type":"string"}},"required":["id","statement","type","status"],"additionalProperties":false}}},"required":["framework","skills"],"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Search available skills for a boost

> Returns a flattened, paginated list of skills matching the search query. Supports $regex and $in operators. Searches skills from frameworks attached to the boost or any of its ancestors. Requires boost admin.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/skills/search":{"post":{"operationId":"boost-searchSkillsAvailableForBoost","summary":"Search available skills for a boost","description":"Returns a flattened, paginated list of skills matching the search query. Supports $regex and $in operators. Searches skills from frameworks attached to the boost or any of its ancestors. Requires boost admin.","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uri":{"type":"string"},"query":{"anyOf":[{"type":"object","properties":{"$or":{"type":"array","items":{"type":"object","properties":{"id":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"statement":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"description":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"code":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"status":{"anyOf":[{"type":"string","enum":["active","archived"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["active","archived"]}}},"required":["$in"]}]}}}}},"required":["$or"]},{"type":"object","properties":{"id":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"statement":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"description":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"code":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"status":{"anyOf":[{"type":"string","enum":["active","archived"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["active","archived"]}}},"required":["$in"]}]}}}]},"limit":{"default":50,"type":"integer","minimum":1,"maximum":200},"cursor":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["uri","query"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"records":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"statement":{"type":"string"},"description":{"type":"string"},"code":{"type":"string"},"icon":{"type":"string"},"type":{"default":"skill","type":"string"},"status":{"default":"active","type":"string","enum":["active","archived"]},"frameworkId":{"type":"string"}},"required":["id","statement","type","status"],"additionalProperties":false}},"hasMore":{"type":"boolean"},"cursor":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["records","hasMore","cursor"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Allocate Bitstring credential status entries

> Allocates Bitstring Status List entries for a credential before it is signed.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/status/allocate":{"post":{"operationId":"boost-allocateCredentialStatus","summary":"Allocate Bitstring credential status entries","description":"Allocates Bitstring Status List entries for a credential before it is signed.","tags":["Boosts"],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"statusPurposes":{"type":"array","items":{"type":"string","enum":["revocation","suspension"]}},"listSize":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","const":"BitstringStatusListEntry"},"statusPurpose":{"type":"string","enum":["revocation","suspension"]},"statusListIndex":{"anyOf":[{"type":"string"},{"type":"integer","minimum":0,"maximum":9007199254740991}]},"statusListCredential":{"type":"string"}},"required":["id","type","statusPurpose","statusListIndex","statusListCredential"],"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Send a Boost

> This endpoint sends a boost to a profile

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/send/{profileId}":{"post":{"operationId":"boost-sendBoost","summary":"Send a Boost","description":"This endpoint sends a boost to a profile","tags":["Boosts"],"parameters":[{"in":"path","name":"profileId","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uri":{"type":"string"},"credential":{"anyOf":[{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]}]},"options":{"type":"object","properties":{"skipNotification":{"default":false,"type":"boolean"}}}},"required":["uri","credential"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Creates a boost

> This route creates a boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/create":{"post":{"operationId":"boost-createBoost","summary":"Creates a boost","description":"This route creates a boost","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string"},"category":{"type":"string"},"created":{"type":"string"},"status":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},"autoConnectRecipients":{"type":"boolean"},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"allowAnyoneToCreateChildren":{"type":"boolean"},"credential":{"anyOf":[{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]}},"required":["@context","type","issuer","credentialSubject"],"additionalProperties":{}}]},"claimPermissions":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}}},"defaultPermissions":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}}},"skills":{"minItems":1,"type":"array","items":{"type":"object","properties":{"frameworkId":{"type":"string"},"id":{"type":"string"},"proficiencyLevel":{"type":"number"}},"required":["frameworkId","id"]}}},"required":["credential"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Creates a boost

> This route creates a boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/create/child":{"post":{"operationId":"boost-createChildBoost","summary":"Creates a boost","description":"This route creates a boost","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"parentUri":{"type":"string"},"boost":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string"},"category":{"type":"string"},"created":{"type":"string"},"status":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},"autoConnectRecipients":{"type":"boolean"},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"allowAnyoneToCreateChildren":{"type":"boolean"},"credential":{"anyOf":[{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]}},"required":["@context","type","issuer","credentialSubject"],"additionalProperties":{}}]},"claimPermissions":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}}},"defaultPermissions":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}}}},"required":["credential"]},"skills":{"type":"array","items":{"type":"object","properties":{"frameworkId":{"type":"string"},"id":{"type":"string"},"proficiencyLevel":{"type":"number"}},"required":["frameworkId","id"]}}},"required":["parentUri","boost"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get boost

> This endpoint gets metadata about a boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost":{"get":{"operationId":"boost-getBoost","summary":"Get boost","description":"This endpoint gets metadata about a boost","tags":["Boosts"],"parameters":[{"in":"query","name":"uri","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string"},"category":{"type":"string"},"created":{"type":"string"},"status":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},"autoConnectRecipients":{"type":"boolean"},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"defaultPermissions":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}},"required":["role","canView","canEdit","canIssue","canRevoke","canManagePermissions","canIssueChildren","canCreateChildren","canEditChildren","canRevokeChildren","canManageChildrenPermissions","canViewAnalytics"],"additionalProperties":false},"allowAnyoneToCreateChildren":{"type":"boolean"},"claimPermissions":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}},"required":["role","canView","canEdit","canIssue","canRevoke","canManagePermissions","canIssueChildren","canCreateChildren","canEditChildren","canRevokeChildren","canManageChildrenPermissions","canViewAnalytics"],"additionalProperties":false},"uri":{"type":"string"},"boost":{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"],"additionalProperties":false}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"],"additionalProperties":false}},"required":["type"],"additionalProperties":false},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"],"additionalProperties":false}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"],"additionalProperties":false}},"required":["type","renderSuite","template"],"additionalProperties":false},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"],"additionalProperties":false}},"required":["type","renderSuite","template"],"additionalProperties":false},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]}},"required":["@context","type","issuer","credentialSubject"],"additionalProperties":{}}},"required":["uri","boost"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Update a boost

> This route updates a boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost":{"post":{"operationId":"boost-updateBoost","summary":"Update a boost","description":"This route updates a boost","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uri":{"type":"string"},"updates":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string"},"category":{"type":"string"},"created":{"type":"string"},"status":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},"autoConnectRecipients":{"type":"boolean"},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"allowAnyoneToCreateChildren":{"type":"boolean"},"credential":{"anyOf":[{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]}},"required":["@context","type","issuer","credentialSubject"],"additionalProperties":{}}]},"defaultPermissions":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}}}}},"skills":{"type":"array","items":{"type":"object","properties":{"frameworkId":{"type":"string"},"id":{"type":"string"},"proficiencyLevel":{"type":"number"}},"required":["frameworkId","id"]}}},"required":["uri","updates"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Delete a boost

> This route deletes a boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost":{"delete":{"operationId":"boost-deleteBoost","summary":"Delete a boost","description":"This route deletes a boost","tags":["Boosts"],"parameters":[{"in":"query","name":"uri","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## List frameworks used by a boost (paginated)

> Returns frameworks aligned to a boost via USES\_FRAMEWORK with pagination and optional query filtering. Requires boost admin.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/frameworks":{"post":{"operationId":"boost-getBoostFrameworks","summary":"List frameworks used by a boost (paginated)","description":"Returns frameworks aligned to a boost via USES_FRAMEWORK with pagination and optional query filtering. Requires boost admin.","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uri":{"type":"string"},"limit":{"default":50,"type":"integer","minimum":1,"maximum":200},"cursor":{"anyOf":[{"type":"string"},{"type":"null"}]},"query":{"anyOf":[{"type":"object","properties":{"$or":{"type":"array","items":{"type":"object","properties":{"id":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"description":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"sourceURI":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"status":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}}}}},"required":["$or"]},{"type":"object","properties":{"id":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"description":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"sourceURI":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"status":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}}}]}},"required":["uri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"image":{"type":"string"},"sourceURI":{"type":"string","format":"uri"},"isPublic":{"default":false,"type":"boolean"},"status":{"default":"active","type":"string","enum":["active","archived"]},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","name","isPublic","status"],"additionalProperties":false}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get boosts

> This endpoint gets the current user's boosts.\
> Warning! This route is deprecated and currently has a hard limit of returning only the first 50 boosts. Please use getPaginatedBoosts instead

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/all":{"post":{"operationId":"boost-getBoosts","summary":"Get boosts","description":"This endpoint gets the current user's boosts.\nWarning! This route is deprecated and currently has a hard limit of returning only the first 50 boosts. Please use getPaginatedBoosts instead","tags":["Boosts"],"deprecated":true,"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"anyOf":[{"type":"object","properties":{"$or":{"type":"array","items":{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}}},"required":["$or"]},{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}]}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string"},"category":{"type":"string"},"created":{"type":"string"},"status":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},"autoConnectRecipients":{"type":"boolean"},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"defaultPermissions":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}},"required":["role","canView","canEdit","canIssue","canRevoke","canManagePermissions","canIssueChildren","canCreateChildren","canEditChildren","canRevokeChildren","canManageChildrenPermissions","canViewAnalytics"],"additionalProperties":false},"allowAnyoneToCreateChildren":{"type":"boolean"},"uri":{"type":"string"}},"required":["uri"],"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Count managed boosts

> This endpoint counts the current user's managed boosts.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/count":{"post":{"operationId":"boost-countBoosts","summary":"Count managed boosts","description":"This endpoint counts the current user's managed boosts.","tags":["Boosts"],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"anyOf":[{"type":"object","properties":{"$or":{"type":"array","items":{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}}},"required":["$or"]},{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}]}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"number"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get boosts

> This endpoint gets the current user's boosts

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/paginated":{"post":{"operationId":"boost-getPaginatedBoosts","summary":"Get boosts","description":"This endpoint gets the current user's boosts","tags":["Boosts"],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"sort":{"type":"string"},"query":{"anyOf":[{"type":"object","properties":{"$or":{"type":"array","items":{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}}},"required":["$or"]},{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}]}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"uri":{"type":"string"},"name":{"type":"string"},"type":{"type":"string"},"category":{"type":"string"},"created":{"type":"string"},"status":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},"autoConnectRecipients":{"type":"boolean"},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"claimPermissions":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}},"required":["role","canView","canEdit","canIssue","canRevoke","canManagePermissions","canIssueChildren","canCreateChildren","canEditChildren","canRevokeChildren","canManageChildrenPermissions","canViewAnalytics"],"additionalProperties":false},"defaultPermissions":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}},"required":["role","canView","canEdit","canIssue","canRevoke","canManagePermissions","canIssueChildren","canCreateChildren","canEditChildren","canRevokeChildren","canManageChildrenPermissions","canViewAnalytics"],"additionalProperties":false},"allowAnyoneToCreateChildren":{"type":"boolean"}},"required":["uri"],"additionalProperties":false}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get boost recipients

> This endpoint gets the recipients of a particular boost.\
> Warning! This route is deprecated and currently has a hard limit of returning only the first 50 boosts. Please use getPaginatedBoostRecipients instead

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/recipients":{"get":{"operationId":"boost-getBoostRecipients","summary":"Get boost recipients","description":"This endpoint gets the recipients of a particular boost.\nWarning! This route is deprecated and currently has a hard limit of returning only the first 50 boosts. Please use getPaginatedBoostRecipients instead","tags":["Boosts"],"deprecated":true,"parameters":[{"in":"query","name":"uri","schema":{"type":"string"},"required":true},{"in":"query","name":"limit","schema":{"default":25,"type":"number"}},{"in":"query","name":"skip","schema":{"type":"number"}},{"in":"query","name":"includeUnacceptedBoosts","schema":{"default":true,"type":"boolean"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"to":{"anyOf":[{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false}},"required":["profileId","displayName","shortBio"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}]},"from":{"type":"string"},"received":{"type":"string"},"uri":{"type":"string"},"status":{"type":"string","enum":["active","revoked","suspended"]}},"required":["to","from"],"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get boost recipients

> This endpoint gets the recipients of a particular boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/recipients/paginated":{"post":{"operationId":"boost-getPaginatedBoostRecipients","summary":"Get boost recipients","description":"This endpoint gets the recipients of a particular boost","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"sort":{"type":"string"},"uri":{"type":"string"},"includeUnacceptedBoosts":{"default":true,"type":"boolean"},"query":{"type":"object","properties":{"profileId":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"displayName":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"shortBio":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"bio":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"email":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"websiteLink":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"isServiceProfile":{"type":"boolean"},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}}}},"required":["uri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"to":{"anyOf":[{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false}},"required":["profileId","displayName","shortBio"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}]},"from":{"type":"string"},"received":{"type":"string"},"uri":{"type":"string"},"status":{"type":"string","enum":["active","revoked","suspended"]}},"required":["to","from"],"additionalProperties":false}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get boost recipients count

> This endpoint counts the recipients of a particular boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/recipients/count":{"get":{"operationId":"boost-getBoostRecipientCount","summary":"Get boost recipients count","description":"This endpoint counts the recipients of a particular boost","tags":["Boosts"],"parameters":[{"in":"query","name":"uri","schema":{"type":"string"},"required":true},{"in":"query","name":"includeUnacceptedBoosts","schema":{"default":true,"type":"boolean"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"number"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get connected boost recipients

> This endpoint gets the recipients of a particular boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/recipients/connected/{uri}":{"post":{"operationId":"boost-getConnectedBoostRecipients","summary":"Get connected boost recipients","description":"This endpoint gets the recipients of a particular boost","tags":["Boosts"],"parameters":[{"in":"path","name":"uri","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"sort":{"type":"string"},"includeUnacceptedBoosts":{"default":true,"type":"boolean"},"query":{"type":"object","properties":{"profileId":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"displayName":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"shortBio":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"bio":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"email":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"websiteLink":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"isServiceProfile":{"type":"boolean"},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}}}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"to":{"anyOf":[{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false}},"required":["profileId","displayName","shortBio"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}]},"from":{"type":"string"},"received":{"type":"string"},"uri":{"type":"string"},"status":{"type":"string","enum":["active","revoked","suspended"]}},"required":["to","from"],"additionalProperties":false}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get boost recipients count

> This endpoint counts the recipients of a particular boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/recipients/connected/{uri}/count":{"get":{"operationId":"boost-getConnectedBoostRecipientCount","summary":"Get boost recipients count","description":"This endpoint counts the recipients of a particular boost","tags":["Boosts"],"parameters":[{"in":"path","name":"uri","schema":{"type":"string"},"required":true},{"in":"query","name":"includeUnacceptedBoosts","schema":{"default":true,"type":"boolean"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"number"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Revoke a boost recipient

> Revokes a credential for a specified recipient. This marks the credential as revoked instead of deleting it, and removes any permissions granted via claim hooks.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/recipients/revoke":{"post":{"operationId":"boost-revokeBoostRecipient","summary":"Revoke a boost recipient","description":"Revokes a credential for a specified recipient. This marks the credential as revoked instead of deleting it, and removes any permissions granted via claim hooks.","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"boostUri":{"type":"string"},"recipientProfileId":{"type":"string"},"credentialUri":{"type":"string"}},"required":["boostUri","recipientProfileId"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Suspend a boost recipient

> Temporarily suspends a credential for a specified recipient. Suspension is reversible.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/recipients/suspend":{"post":{"operationId":"boost-suspendBoostRecipient","summary":"Suspend a boost recipient","description":"Temporarily suspends a credential for a specified recipient. Suspension is reversible.","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"boostUri":{"type":"string"},"recipientProfileId":{"type":"string"},"credentialUri":{"type":"string"}},"required":["boostUri","recipientProfileId"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Unsuspend a boost recipient

> Clears a temporary credential suspension for a specified recipient.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/recipients/unsuspend":{"post":{"operationId":"boost-unsuspendBoostRecipient","summary":"Unsuspend a boost recipient","description":"Clears a temporary credential suspension for a specified recipient.","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"boostUri":{"type":"string"},"recipientProfileId":{"type":"string"},"credentialUri":{"type":"string"}},"required":["boostUri","recipientProfileId"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get Profile Managers that are a child of a boost

> Get Profile Managers that are a child of a boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Profile Managers"},{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/children-profile-managers":{"post":{"operationId":"boost-getChildrenProfileManagers","summary":"Get Profile Managers that are a child of a boost","description":"Get Profile Managers that are a child of a boost","tags":["Boosts","Profile Managers"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"sort":{"type":"string"},"uri":{"type":"string"},"query":{"type":"object","properties":{"id":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"displayName":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"shortBio":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"bio":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"email":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}}}},"required":["uri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"created":{"type":"string"},"displayName":{"default":"","type":"string"},"shortBio":{"default":"","type":"string"},"bio":{"default":"","type":"string"},"email":{"type":"string"},"image":{"type":"string"},"heroImage":{"type":"string"},"did":{"type":"string"}},"required":["id","created","did"],"additionalProperties":false}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get boost children

> This endpoint gets the children of a particular boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/children":{"post":{"operationId":"boost-getBoostChildren","summary":"Get boost children","description":"This endpoint gets the children of a particular boost","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"sort":{"type":"string"},"uri":{"type":"string"},"query":{"anyOf":[{"type":"object","properties":{"$or":{"type":"array","items":{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}}},"required":["$or"]},{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}]},"numberOfGenerations":{"default":1,"anyOf":[{"type":"number"},{"type":"number","const":null}]}},"required":["uri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"uri":{"type":"string"},"name":{"type":"string"},"type":{"type":"string"},"category":{"type":"string"},"created":{"type":"string"},"status":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},"autoConnectRecipients":{"type":"boolean"},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"claimPermissions":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}},"required":["role","canView","canEdit","canIssue","canRevoke","canManagePermissions","canIssueChildren","canCreateChildren","canEditChildren","canRevokeChildren","canManageChildrenPermissions","canViewAnalytics"],"additionalProperties":false},"defaultPermissions":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}},"required":["role","canView","canEdit","canIssue","canRevoke","canManagePermissions","canIssueChildren","canCreateChildren","canEditChildren","canRevokeChildren","canManageChildrenPermissions","canViewAnalytics"],"additionalProperties":false},"allowAnyoneToCreateChildren":{"type":"boolean"}},"required":["uri"],"additionalProperties":false}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get boost recipients with children

> This endpoint gets the recipients of a boost and all its children boosts

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/recipients-with-children/paginated":{"post":{"operationId":"boost-getPaginatedBoostRecipientsWithChildren","summary":"Get boost recipients with children","description":"This endpoint gets the recipients of a boost and all its children boosts","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"sort":{"type":"string"},"uri":{"type":"string"},"includeUnacceptedBoosts":{"default":true,"type":"boolean"},"numberOfGenerations":{"default":1,"anyOf":[{"type":"number"},{"type":"number","const":null}]},"boostQuery":{"anyOf":[{"type":"object","properties":{"$or":{"type":"array","items":{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}}},"required":["$or"]},{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}]},"profileQuery":{"type":"object","properties":{"profileId":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"displayName":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"shortBio":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"bio":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"email":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"websiteLink":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"isServiceProfile":{"type":"boolean"},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}}}},"required":["uri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"to":{"anyOf":[{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false}},"required":["profileId","displayName","shortBio"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}]},"from":{"type":"string"},"received":{"type":"string"},"boostUris":{"type":"array","items":{"type":"string"}},"credentialUris":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["active","revoked","suspended"]}},"required":["to","from","boostUris"],"additionalProperties":false}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Count boost recipients with children

> This endpoint counts distinct recipients of a boost and all its children boosts

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/recipients-with-children/count":{"post":{"operationId":"boost-getBoostRecipientsWithChildrenCount","summary":"Count boost recipients with children","description":"This endpoint counts distinct recipients of a boost and all its children boosts","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uri":{"type":"string"},"includeUnacceptedBoosts":{"default":true,"type":"boolean"},"numberOfGenerations":{"default":1,"anyOf":[{"type":"number"},{"type":"number","const":null}]},"boostQuery":{"anyOf":[{"type":"object","properties":{"$or":{"type":"array","items":{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}}},"required":["$or"]},{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}]},"profileQuery":{"type":"object","properties":{"profileId":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"displayName":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"shortBio":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"bio":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"email":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"websiteLink":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"isServiceProfile":{"type":"boolean"},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}}}},"required":["uri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"number"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Count boost children

> This endpoint counts the children of a particular boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/children/count":{"post":{"operationId":"boost-countBoostChildren","summary":"Count boost children","description":"This endpoint counts the children of a particular boost","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uri":{"type":"string"},"query":{"anyOf":[{"type":"object","properties":{"$or":{"type":"array","items":{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}}},"required":["$or"]},{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}]},"numberOfGenerations":{"default":1,"anyOf":[{"type":"number"},{"type":"number","const":null}]}},"required":["uri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"number"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get boost siblings

> This endpoint gets the siblings of a particular boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/siblings":{"post":{"operationId":"boost-getBoostSiblings","summary":"Get boost siblings","description":"This endpoint gets the siblings of a particular boost","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"sort":{"type":"string"},"uri":{"type":"string"},"query":{"anyOf":[{"type":"object","properties":{"$or":{"type":"array","items":{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}}},"required":["$or"]},{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}]}},"required":["uri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"uri":{"type":"string"},"name":{"type":"string"},"type":{"type":"string"},"category":{"type":"string"},"created":{"type":"string"},"status":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},"autoConnectRecipients":{"type":"boolean"},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"claimPermissions":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}},"required":["role","canView","canEdit","canIssue","canRevoke","canManagePermissions","canIssueChildren","canCreateChildren","canEditChildren","canRevokeChildren","canManageChildrenPermissions","canViewAnalytics"],"additionalProperties":false},"defaultPermissions":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}},"required":["role","canView","canEdit","canIssue","canRevoke","canManagePermissions","canIssueChildren","canCreateChildren","canEditChildren","canRevokeChildren","canManageChildrenPermissions","canViewAnalytics"],"additionalProperties":false},"allowAnyoneToCreateChildren":{"type":"boolean"}},"required":["uri"],"additionalProperties":false}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Count boost siblings

> This endpoint counts the siblings of a particular boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/siblings/count":{"post":{"operationId":"boost-countBoostSiblings","summary":"Count boost siblings","description":"This endpoint counts the siblings of a particular boost","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uri":{"type":"string"},"query":{"anyOf":[{"type":"object","properties":{"$or":{"type":"array","items":{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}}},"required":["$or"]},{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}]}},"required":["uri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"number"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get familial boosts

> This endpoint gets the parents, children, and siblings of a particular boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/family":{"post":{"operationId":"boost-getFamilialBoosts","summary":"Get familial boosts","description":"This endpoint gets the parents, children, and siblings of a particular boost","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"sort":{"type":"string"},"uri":{"type":"string"},"query":{"anyOf":[{"type":"object","properties":{"$or":{"type":"array","items":{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}}},"required":["$or"]},{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}]},"parentGenerations":{"default":1,"anyOf":[{"type":"number"},{"type":"number","const":null}]},"childGenerations":{"default":1,"anyOf":[{"type":"number"},{"type":"number","const":null}]},"includeExtendedFamily":{"default":false,"type":"boolean"}},"required":["uri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"uri":{"type":"string"},"name":{"type":"string"},"type":{"type":"string"},"category":{"type":"string"},"created":{"type":"string"},"status":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},"autoConnectRecipients":{"type":"boolean"},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"claimPermissions":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}},"required":["role","canView","canEdit","canIssue","canRevoke","canManagePermissions","canIssueChildren","canCreateChildren","canEditChildren","canRevokeChildren","canManageChildrenPermissions","canViewAnalytics"],"additionalProperties":false},"defaultPermissions":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}},"required":["role","canView","canEdit","canIssue","canRevoke","canManagePermissions","canIssueChildren","canCreateChildren","canEditChildren","canRevokeChildren","canManageChildrenPermissions","canViewAnalytics"],"additionalProperties":false},"allowAnyoneToCreateChildren":{"type":"boolean"}},"required":["uri"],"additionalProperties":false}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Count familial boosts

> This endpoint counts the parents, children, and siblings of a particular boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/family/count":{"post":{"operationId":"boost-countFamilialBoosts","summary":"Count familial boosts","description":"This endpoint counts the parents, children, and siblings of a particular boost","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uri":{"type":"string"},"query":{"anyOf":[{"type":"object","properties":{"$or":{"type":"array","items":{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}}},"required":["$or"]},{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}]},"parentGenerations":{"default":1,"anyOf":[{"type":"number"},{"type":"number","const":null}]},"childGenerations":{"default":1,"anyOf":[{"type":"number"},{"type":"number","const":null}]},"includeExtendedFamily":{"default":false,"type":"boolean"}},"required":["uri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"number"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get boost parents

> This endpoint gets the parents of a particular boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/parents":{"post":{"operationId":"boost-getBoostParents","summary":"Get boost parents","description":"This endpoint gets the parents of a particular boost","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"sort":{"type":"string"},"uri":{"type":"string"},"query":{"anyOf":[{"type":"object","properties":{"$or":{"type":"array","items":{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}}},"required":["$or"]},{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}]},"numberOfGenerations":{"default":1,"anyOf":[{"type":"number"},{"type":"number","const":null}]}},"required":["uri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"uri":{"type":"string"},"name":{"type":"string"},"type":{"type":"string"},"category":{"type":"string"},"created":{"type":"string"},"status":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},"autoConnectRecipients":{"type":"boolean"},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"claimPermissions":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}},"required":["role","canView","canEdit","canIssue","canRevoke","canManagePermissions","canIssueChildren","canCreateChildren","canEditChildren","canRevokeChildren","canManageChildrenPermissions","canViewAnalytics"],"additionalProperties":false},"defaultPermissions":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}},"required":["role","canView","canEdit","canIssue","canRevoke","canManagePermissions","canIssueChildren","canCreateChildren","canEditChildren","canRevokeChildren","canManageChildrenPermissions","canViewAnalytics"],"additionalProperties":false},"allowAnyoneToCreateChildren":{"type":"boolean"}},"required":["uri"],"additionalProperties":false}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Count boost parents

> This endpoint counts the parents of a particular boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/parents/count":{"post":{"operationId":"boost-countBoostParents","summary":"Count boost parents","description":"This endpoint counts the parents of a particular boost","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uri":{"type":"string"},"query":{"anyOf":[{"type":"object","properties":{"$or":{"type":"array","items":{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}}},"required":["$or"]},{"type":"object","properties":{"uri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"category":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"meta":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}},"status":{"anyOf":[{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string","enum":["DRAFT","PROVISIONAL","LIVE"]}}},"required":["$in"]}]},"autoConnectRecipients":{"type":"boolean"}}}]},"numberOfGenerations":{"default":1,"anyOf":[{"type":"number"},{"type":"number","const":null}]}},"required":["uri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"number"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get boost admins

> This route returns the admins for a boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/admins":{"post":{"operationId":"boost-getBoostAdmins","summary":"Get boost admins","description":"This route returns the admins for a boost","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"sort":{"type":"string"},"includeSelf":{"default":true,"type":"boolean"},"uri":{"type":"string"}},"required":["uri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"anyOf":[{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false}},"required":["profileId","displayName","shortBio"],"additionalProperties":false},{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}]}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Add a Boost admin

> This route adds a new admin for a boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/add-admin":{"post":{"operationId":"boost-addBoostAdmin","summary":"Add a Boost admin","description":"This route adds a new admin for a boost","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uri":{"type":"string"},"profileId":{"type":"string"}},"required":["uri","profileId"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Remove a Boost admin

> This route removes an  admin from a boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/remove-admin":{"post":{"operationId":"boost-removeBoostAdmin","summary":"Remove a Boost admin","description":"This route removes an  admin from a boost","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uri":{"type":"string"},"profileId":{"type":"string"}},"required":["uri","profileId"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get boost permissions

> This endpoint gets permission metadata about a boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/permissions":{"get":{"operationId":"boost-getBoostPermissions","summary":"Get boost permissions","description":"This endpoint gets permission metadata about a boost","tags":["Boosts"],"parameters":[{"in":"query","name":"uri","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}},"required":["role","canView","canEdit","canIssue","canRevoke","canManagePermissions","canIssueChildren","canCreateChildren","canEditChildren","canRevokeChildren","canManageChildrenPermissions","canViewAnalytics"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Update boost permissions

> This endpoint updates permission metadata about a boost for the current user

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/permissions":{"post":{"operationId":"boost-updateBoostPermissions","summary":"Update boost permissions","description":"This endpoint updates permission metadata about a boost for the current user","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uri":{"type":"string"},"updates":{"type":"object","properties":{"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}}}},"required":["uri","updates"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get boost permissions for someone else

> This endpoint gets permission metadata about a boost for someone else

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/permissions/{profileId}":{"get":{"operationId":"boost-getOtherBoostPermissions","summary":"Get boost permissions for someone else","description":"This endpoint gets permission metadata about a boost for someone else","tags":["Boosts"],"parameters":[{"in":"path","name":"profileId","schema":{"type":"string"},"required":true},{"in":"query","name":"uri","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}},"required":["role","canView","canEdit","canIssue","canRevoke","canManagePermissions","canIssueChildren","canCreateChildren","canEditChildren","canRevokeChildren","canManageChildrenPermissions","canViewAnalytics"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Update other profile's boost permissions

> This endpoint updates permission metadata about a boost for another user

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/permissions/{profileId}":{"post":{"operationId":"boost-updateOtherBoostPermissions","summary":"Update other profile's boost permissions","description":"This endpoint updates permission metadata about a boost for another user","tags":["Boosts"],"parameters":[{"in":"path","name":"profileId","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uri":{"type":"string"},"updates":{"type":"object","properties":{"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}}}},"required":["uri","updates"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Generate a claim link for a boost

> This route creates a challenge that an unknown profile can use to claim a boost.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/generate-claim-link":{"post":{"operationId":"boost-generateClaimLink","summary":"Generate a claim link for a boost","description":"This route creates a challenge that an unknown profile can use to claim a boost.","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"boostUri":{"type":"string"},"challenge":{"type":"string"},"claimLinkSA":{"type":"object","properties":{"endpoint":{"type":"string"},"name":{"type":"string"},"did":{"type":"string"}},"required":["endpoint","name"]},"options":{"type":"object","properties":{"ttlSeconds":{"type":"number"},"totalUses":{"type":"number"}}}},"required":["boostUri","claimLinkSA"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"boostUri":{"type":"string"},"challenge":{"type":"string"}},"required":["boostUri","challenge"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Claim a boost using a claim link

> Claims a boost using a claim link, including a challenge

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/claim":{"post":{"operationId":"boost-claimBoostWithLink","summary":"Claim a boost using a claim link","description":"Claims a boost using a claim link, including a challenge","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"boostUri":{"type":"string"},"challenge":{"type":"string"}},"required":["boostUri","challenge"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Make Boost Parent

> This endpoint creates a parent/child relationship between two boosts

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/make-parent":{"post":{"operationId":"boost-makeBoostParent","summary":"Make Boost Parent","description":"This endpoint creates a parent/child relationship between two boosts","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"parentUri":{"type":"string"},"childUri":{"type":"string"}},"required":["parentUri","childUri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Remove Boost Parent

> This endpoint removes a parent/child relationship between two boosts

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/remove-parent":{"post":{"operationId":"boost-removeBoostParent","summary":"Remove Boost Parent","description":"This endpoint removes a parent/child relationship between two boosts","tags":["Boosts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"parentUri":{"type":"string"},"childUri":{"type":"string"}},"required":["parentUri","childUri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Send a boost to a profile using a signing authority

> Issues a boost VC to a recipient profile using a specified signing authority and sends it via the network.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Boosts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/boost/send/via-signing-authority/{profileId}":{"post":{"operationId":"boost-sendBoostViaSigningAuthority","summary":"Send a boost to a profile using a signing authority","description":"Issues a boost VC to a recipient profile using a specified signing authority and sends it via the network.","tags":["Boosts"],"parameters":[{"in":"path","name":"profileId","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"boostUri":{"type":"string"},"signingAuthority":{"type":"object","properties":{"name":{"type":"string"},"endpoint":{"type":"string"}},"required":["name","endpoint"]},"templateData":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"options":{"type":"object","properties":{"skipNotification":{"default":false,"type":"boolean"}}}},"required":["boostUri","signingAuthority"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```


# Presentations

## Send a Presentation

> This endpoint sends a presentation to a user based on their profileId

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Presentations"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/presentation/send/{profileId}":{"post":{"operationId":"presentation-sendPresentation","summary":"Send a Presentation","description":"This endpoint sends a presentation to a user based on their profileId","tags":["Presentations"],"parameters":[{"in":"path","name":"profileId","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"presentation":{"anyOf":[{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"verifiableCredential":{"anyOf":[{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}}}]},"holder":{"type":"string"},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","proof"],"additionalProperties":{}},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]}]}},"required":["presentation"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Accept a Presentation

> This endpoint accepts a presentation

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Presentations"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/presentation/accept":{"post":{"operationId":"presentation-acceptPresentation","summary":"Accept a Presentation","description":"This endpoint accepts a presentation","tags":["Presentations"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uri":{"type":"string"}},"required":["uri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get received presentations

> This endpoint returns the current user's received presentations

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Presentations"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/presentation/received":{"get":{"operationId":"presentation-receivedPresentations","summary":"Get received presentations","description":"This endpoint returns the current user's received presentations","tags":["Presentations"],"parameters":[{"in":"query","name":"limit","schema":{"default":25,"type":"integer","exclusiveMinimum":0,"exclusiveMaximum":100}},{"in":"query","name":"from","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"uri":{"type":"string"},"to":{"type":"string"},"from":{"type":"string"},"sent":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"received":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"metadata":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["uri","to","from","sent"],"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get sent presentations

> This endpoint returns the current user's sent presentations

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Presentations"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/presentation/sent":{"get":{"operationId":"presentation-sentPresentations","summary":"Get sent presentations","description":"This endpoint returns the current user's sent presentations","tags":["Presentations"],"parameters":[{"in":"query","name":"limit","schema":{"default":25,"type":"integer","exclusiveMinimum":0,"exclusiveMaximum":100}},{"in":"query","name":"to","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"uri":{"type":"string"},"to":{"type":"string"},"from":{"type":"string"},"sent":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"received":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"metadata":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["uri","to","from","sent"],"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get incoming presentations

> This endpoint returns the current user's incoming presentations

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Presentations"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/presentation/incoming":{"get":{"operationId":"presentation-incomingPresentations","summary":"Get incoming presentations","description":"This endpoint returns the current user's incoming presentations","tags":["Presentations"],"parameters":[{"in":"query","name":"limit","schema":{"default":25,"type":"integer","exclusiveMinimum":0,"exclusiveMaximum":100}},{"in":"query","name":"from","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"uri":{"type":"string"},"to":{"type":"string"},"from":{"type":"string"},"sent":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"received":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"metadata":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["uri","to","from","sent"],"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Delete a presentation

> This endpoint deletes a presentation

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Presentations"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/presentation":{"delete":{"operationId":"presentation-deletePresentation","summary":"Delete a presentation","description":"This endpoint deletes a presentation","tags":["Presentations"],"parameters":[{"in":"query","name":"uri","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```


# Storage

## Store a Credential/Presentation

> This endpoint stores a credential/presentation, returning a uri that can be used to resolve it

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Storage"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/storage/store":{"post":{"operationId":"storage-store","summary":"Store a Credential/Presentation","description":"This endpoint stores a credential/presentation, returning a uri that can be used to resolve it","tags":["Storage"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"item":{"anyOf":[{"anyOf":[{"anyOf":[{"anyOf":[{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]}},"required":["@context","type","issuer","credentialSubject"],"additionalProperties":{}},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}}]},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"verifiableCredential":{"anyOf":[{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}}}]},"holder":{"type":"string"},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","proof"],"additionalProperties":{}}]},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]}]},{"type":"object","properties":{"format":{"type":"string","enum":["w3c-vc-2.0","w3c-vc-1.1","jwt-vc-json","dc+sd-jwt","vc+sd-jwt","mso_mdoc"]},"data":{"type":"string"}},"required":["format","data"],"additionalProperties":{}}]},"type":{"type":"string","enum":["credential","presentation"]}},"required":["item"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Resolves a URI to a Credential/Presentation

> This endpoint stores a credential/presentation, returning a uri that can be used to resolve it

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Storage"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/storage/resolve":{"get":{"operationId":"storage-resolve","summary":"Resolves a URI to a Credential/Presentation","description":"This endpoint stores a credential/presentation, returning a uri that can be used to resolve it","tags":["Storage"],"parameters":[{"in":"query","name":"uri","schema":{"type":"string"},"required":true},{"in":"query","name":"challenge","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"anyOf":[{"anyOf":[{"anyOf":[{"anyOf":[{"anyOf":[{"anyOf":[{"type":"object","properties":{"format":{"type":"string","enum":["w3c-vc-2.0","w3c-vc-1.1","jwt-vc-json","dc+sd-jwt","vc+sd-jwt","mso_mdoc"]},"data":{"type":"string"}},"required":["format","data"],"additionalProperties":{}},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"],"additionalProperties":false}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"],"additionalProperties":false}},"required":["type"],"additionalProperties":false},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"],"additionalProperties":false}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"],"additionalProperties":false}},"required":["type","renderSuite","template"],"additionalProperties":false},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"],"additionalProperties":false}},"required":["type","renderSuite","template"],"additionalProperties":false},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]}},"required":["@context","type","issuer","credentialSubject"],"additionalProperties":{}}]},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"],"additionalProperties":false}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"],"additionalProperties":false}},"required":["type"],"additionalProperties":false},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"],"additionalProperties":false}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"],"additionalProperties":false}},"required":["type","renderSuite","template"],"additionalProperties":false},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"],"additionalProperties":false}},"required":["type","renderSuite","template"],"additionalProperties":false},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}}]},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"verifiableCredential":{"anyOf":[{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"],"additionalProperties":false}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"],"additionalProperties":false}},"required":["type"],"additionalProperties":false},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"],"additionalProperties":false}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"],"additionalProperties":false}},"required":["type","renderSuite","template"],"additionalProperties":false},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"],"additionalProperties":false}},"required":["type","renderSuite","template"],"additionalProperties":false},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"],"additionalProperties":false}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"],"additionalProperties":false}},"required":["type"],"additionalProperties":false},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"],"additionalProperties":false}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"],"additionalProperties":false}},"required":["type","renderSuite","template"],"additionalProperties":false},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"],"additionalProperties":false}},"required":["type","renderSuite","template"],"additionalProperties":false},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}}}]},"holder":{"type":"string"},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","proof"],"additionalProperties":{}}]},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"additionalProperties":false},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"],"additionalProperties":false},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"],"additionalProperties":false}}},"required":["protected","iv","ciphertext","tag"],"additionalProperties":false}]},{"type":"object","properties":{"read":{"type":"object","properties":{"anonymize":{"type":"boolean"},"credentials":{"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["credentials","personal"],"additionalProperties":false},"write":{"type":"object","properties":{"credentials":{"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["credentials","personal"],"additionalProperties":false}},"required":["read","write"],"additionalProperties":false}]},{"type":"object","properties":{"read":{"type":"object","properties":{"anonymize":{"type":"boolean"},"credentials":{"type":"object","properties":{"shareAll":{"type":"boolean"},"sharing":{"type":"boolean"},"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"sharing":{"type":"boolean"},"shared":{"type":"array","items":{"type":"string"}},"shareAll":{"type":"boolean"},"shareUntil":{"type":"string"}},"additionalProperties":false}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}}},"required":["credentials","personal"],"additionalProperties":false},"write":{"type":"object","properties":{"credentials":{"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}},"required":["credentials","personal"],"additionalProperties":false},"deniedWriters":{"type":"array","items":{"type":"string"}}},"required":["read","write"],"additionalProperties":false}]}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```


# Contracts

## Get Consent Flow Contracts

> Gets Consent Flow Contract Details

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contract":{"get":{"operationId":"contracts-getConsentFlowContract","summary":"Get Consent Flow Contracts","description":"Gets Consent Flow Contract Details","tags":["Contracts"],"parameters":[{"in":"query","name":"uri","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"contract":{"type":"object","properties":{"read":{"type":"object","properties":{"anonymize":{"type":"boolean"},"credentials":{"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["credentials","personal"],"additionalProperties":false},"write":{"type":"object","properties":{"credentials":{"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["credentials","personal"],"additionalProperties":false}},"required":["read","write"],"additionalProperties":false},"owner":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},"name":{"type":"string"},"subtitle":{"type":"string"},"description":{"type":"string"},"reasonForAccessing":{"type":"string"},"image":{"type":"string"},"uri":{"type":"string"},"needsGuardianConsent":{"type":"boolean"},"redirectUrl":{"type":"string"},"frontDoorBoostUri":{"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"expiresAt":{"type":"string"},"autoBoosts":{"type":"array","items":{"type":"string"}},"writers":{"type":"array","items":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}}},"required":["contract","owner","name","uri","createdAt","updatedAt"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Create Consent Flow Contract

> Creates a Consent Flow Contract for a profile

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contract":{"post":{"operationId":"contracts-createConsentFlowContract","summary":"Create Consent Flow Contract","description":"Creates a Consent Flow Contract for a profile","tags":["Contracts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contract":{"type":"object","properties":{"read":{"default":{"credentials":{"categories":{}},"personal":{}},"type":"object","properties":{"anonymize":{"type":"boolean"},"credentials":{"default":{"categories":{}},"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"]}}}},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"]}}}},"write":{"default":{"credentials":{"categories":{}},"personal":{}},"type":"object","properties":{"credentials":{"default":{"categories":{}},"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"]}}}},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"]}}}}}},"name":{"type":"string"},"subtitle":{"type":"string"},"description":{"type":"string"},"reasonForAccessing":{"type":"string"},"needsGuardianConsent":{"type":"boolean"},"redirectUrl":{"type":"string"},"frontDoorBoostUri":{"type":"string"},"image":{"type":"string"},"expiresAt":{"type":"string"},"autoboosts":{"type":"array","items":{"type":"object","properties":{"boostUri":{"type":"string"},"signingAuthority":{"type":"object","properties":{"endpoint":{"type":"string"},"name":{"type":"string"}},"required":["endpoint","name"]}},"required":["boostUri","signingAuthority"]}},"writers":{"type":"array","items":{"type":"string"}}},"required":["contract","name"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Delete a Consent Flow Contract

> This route deletes a Consent Flow Contract

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contract":{"delete":{"operationId":"contracts-deleteConsentFlowContract","summary":"Delete a Consent Flow Contract","description":"This route deletes a Consent Flow Contract","tags":["Contracts"],"parameters":[{"in":"query","name":"uri","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get Consent Flow Contracts

> Gets Consent Flow Contracts for a profile

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contracts":{"post":{"operationId":"contracts-getConsentFlowContracts","summary":"Get Consent Flow Contracts","description":"Gets Consent Flow Contracts for a profile","tags":["Contracts"],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"sort":{"type":"string"},"query":{"default":{},"type":"object","properties":{"read":{"type":"object","properties":{"anonymize":{"type":"boolean"},"credentials":{"type":"object","properties":{"categories":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"}}}}}},"personal":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"}}}}}},"write":{"type":"object","properties":{"credentials":{"type":"object","properties":{"categories":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"}}}}}},"personal":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"}}}}}}}}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"contract":{"type":"object","properties":{"read":{"type":"object","properties":{"anonymize":{"type":"boolean"},"credentials":{"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["credentials","personal"],"additionalProperties":false},"write":{"type":"object","properties":{"credentials":{"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["credentials","personal"],"additionalProperties":false}},"required":["read","write"],"additionalProperties":false},"name":{"type":"string"},"subtitle":{"type":"string"},"description":{"type":"string"},"reasonForAccessing":{"type":"string"},"image":{"type":"string"},"uri":{"type":"string"},"needsGuardianConsent":{"type":"boolean"},"redirectUrl":{"type":"string"},"frontDoorBoostUri":{"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"expiresAt":{"type":"string"},"autoBoosts":{"type":"array","items":{"type":"string"}},"writers":{"type":"array","items":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}}},"required":["contract","name","uri","createdAt","updatedAt"],"additionalProperties":false}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get the data that has been consented for a contract

> This route grabs all the data that has been consented for a contract

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contract/data-for-contract":{"post":{"operationId":"contracts-getConsentedDataForContract","summary":"Get the data that has been consented for a contract","description":"This route grabs all the data that has been consented for a contract","tags":["Contracts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"sort":{"type":"string"},"uri":{"type":"string"},"query":{"default":{},"type":"object","properties":{"anonymize":{"type":"boolean"},"credentials":{"type":"object","properties":{"categories":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}}},"personal":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}}}},"required":["uri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"credentials":{"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"array","items":{"type":"string"}}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"date":{"type":"string"}},"required":["credentials","personal","date"],"additionalProperties":false}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get the data that has been consented by a did

> This route grabs all the data that has been consented by a did

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contract/data-for-did":{"post":{"operationId":"contracts-getConsentedDataForDid","summary":"Get the data that has been consented by a did","description":"This route grabs all the data that has been consented by a did","tags":["Contracts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"sort":{"type":"string"},"did":{"type":"string"},"query":{"type":"object","properties":{"credentials":{"type":"object","properties":{"categories":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}}},"personal":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}},"id":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]}}}},"required":["did"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"credentials":{"type":"array","items":{"type":"object","properties":{"category":{"type":"string"},"uri":{"type":"string"}},"required":["category","uri"],"additionalProperties":false}},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"date":{"type":"string"},"contractUri":{"type":"string"}},"required":["credentials","personal","date","contractUri"],"additionalProperties":false}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get the data that has been consented for all of your contracts

> This route grabs all the data that has been consented for all of your contracts

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contract/data":{"post":{"operationId":"contracts-getConsentedData","summary":"Get the data that has been consented for all of your contracts","description":"This route grabs all the data that has been consented for all of your contracts","tags":["Contracts"],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"sort":{"type":"string"},"query":{"default":{},"type":"object","properties":{"anonymize":{"type":"boolean"},"credentials":{"type":"object","properties":{"categories":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}}},"personal":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}}}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"credentials":{"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"array","items":{"type":"string"}}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"date":{"type":"string"}},"required":["credentials","personal","date"],"additionalProperties":false}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Writes a boost credential to a did that has consented to a contract

> Writes a boost credential to a did that has consented to a contract

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contract/write":{"post":{"operationId":"contracts-writeCredentialToContract","summary":"Writes a boost credential to a did that has consented to a contract","description":"Writes a boost credential to a did that has consented to a contract","tags":["Contracts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"did":{"type":"string"},"contractUri":{"type":"string"},"boostUri":{"type":"string"},"credential":{"anyOf":[{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]}]}},"required":["did","contractUri","boostUri","credential"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Write credential through signing authority for a DID consented to a contract

> Issues and sends a boost credential via a registered signing authority to a DID that has consented to a contract.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contract/write/via-signing-authority":{"post":{"operationId":"contracts-writeCredentialToContractViaSigningAuthority","summary":"Write credential through signing authority for a DID consented to a contract","description":"Issues and sends a boost credential via a registered signing authority to a DID that has consented to a contract.","tags":["Contracts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"did":{"type":"string"},"contractUri":{"type":"string"},"boostUri":{"type":"string"},"signingAuthority":{"type":"object","properties":{"name":{"type":"string"},"endpoint":{"type":"string"}},"required":["name","endpoint"]}},"required":["did","contractUri","boostUri","signingAuthority"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Consent To Contract

> Consents to a Contract with a hard set of terms

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contract/consent":{"post":{"operationId":"contracts-consentToContract","summary":"Consent To Contract","description":"Consents to a Contract with a hard set of terms","tags":["Contracts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"terms":{"type":"object","properties":{"read":{"default":{"credentials":{"categories":{}},"personal":{}},"type":"object","properties":{"anonymize":{"type":"boolean"},"credentials":{"default":{"categories":{}},"type":"object","properties":{"shareAll":{"type":"boolean"},"sharing":{"type":"boolean"},"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"sharing":{"type":"boolean"},"shared":{"type":"array","items":{"type":"string"}},"shareAll":{"type":"boolean"},"shareUntil":{"type":"string"}}}}}},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}}}},"write":{"default":{"credentials":{"categories":{}},"personal":{}},"type":"object","properties":{"credentials":{"default":{"categories":{}},"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}}},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}}},"deniedWriters":{"type":"array","items":{"type":"string"}}}},"contractUri":{"type":"string"},"expiresAt":{"type":"string"},"oneTime":{"type":"boolean"},"recipientToken":{"type":"string"}},"required":["terms","contractUri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"termsUri":{"type":"string"},"redirectUrl":{"type":"string"}},"required":["termsUri"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Gets Consented Contracts

> Gets all consented contracts for a user

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contracts/consent":{"post":{"operationId":"contracts-getConsentedContracts","summary":"Gets Consented Contracts","description":"Gets all consented contracts for a user","tags":["Contracts"],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"sort":{"type":"string"},"query":{"default":{},"type":"object","properties":{"read":{"type":"object","properties":{"anonymize":{"type":"boolean"},"credentials":{"type":"object","properties":{"shareAll":{"type":"boolean"},"sharing":{"type":"boolean"},"categories":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"sharing":{"type":"boolean"},"shared":{"type":"array","items":{"type":"string"}},"shareAll":{"type":"boolean"},"shareUntil":{"type":"string"}}}}}},"personal":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}}}},"write":{"type":"object","properties":{"credentials":{"type":"object","properties":{"categories":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}}},"personal":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}}}}}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"expiresAt":{"type":"string"},"oneTime":{"type":"boolean"},"terms":{"type":"object","properties":{"read":{"type":"object","properties":{"anonymize":{"type":"boolean"},"credentials":{"type":"object","properties":{"shareAll":{"type":"boolean"},"sharing":{"type":"boolean"},"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"sharing":{"type":"boolean"},"shared":{"type":"array","items":{"type":"string"}},"shareAll":{"type":"boolean"},"shareUntil":{"type":"string"}},"additionalProperties":false}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}}},"required":["credentials","personal"],"additionalProperties":false},"write":{"type":"object","properties":{"credentials":{"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}},"required":["credentials","personal"],"additionalProperties":false},"deniedWriters":{"type":"array","items":{"type":"string"}}},"required":["read","write"],"additionalProperties":false},"contract":{"type":"object","properties":{"contract":{"type":"object","properties":{"read":{"type":"object","properties":{"anonymize":{"type":"boolean"},"credentials":{"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["credentials","personal"],"additionalProperties":false},"write":{"type":"object","properties":{"credentials":{"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["credentials","personal"],"additionalProperties":false}},"required":["read","write"],"additionalProperties":false},"owner":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},"name":{"type":"string"},"subtitle":{"type":"string"},"description":{"type":"string"},"reasonForAccessing":{"type":"string"},"image":{"type":"string"},"uri":{"type":"string"},"needsGuardianConsent":{"type":"boolean"},"redirectUrl":{"type":"string"},"frontDoorBoostUri":{"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"expiresAt":{"type":"string"},"autoBoosts":{"type":"array","items":{"type":"string"}},"writers":{"type":"array","items":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false}}},"required":["contract","owner","name","uri","createdAt","updatedAt"],"additionalProperties":false},"uri":{"type":"string"},"consenter":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},"status":{"type":"string","enum":["live","stale","withdrawn"]}},"required":["terms","contract","uri","consenter","status"],"additionalProperties":false}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Updates Contract Terms

> Updates the terms for a consented contract

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contract/consent/update":{"post":{"operationId":"contracts-updateConsentedContractTerms","summary":"Updates Contract Terms","description":"Updates the terms for a consented contract","tags":["Contracts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uri":{"type":"string"},"terms":{"type":"object","properties":{"read":{"default":{"credentials":{"categories":{}},"personal":{}},"type":"object","properties":{"anonymize":{"type":"boolean"},"credentials":{"default":{"categories":{}},"type":"object","properties":{"shareAll":{"type":"boolean"},"sharing":{"type":"boolean"},"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"sharing":{"type":"boolean"},"shared":{"type":"array","items":{"type":"string"}},"shareAll":{"type":"boolean"},"shareUntil":{"type":"string"}}}}}},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}}}},"write":{"default":{"credentials":{"categories":{}},"personal":{}},"type":"object","properties":{"credentials":{"default":{"categories":{}},"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}}},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}}},"deniedWriters":{"type":"array","items":{"type":"string"}}}},"expiresAt":{"type":"string"},"oneTime":{"type":"boolean"}},"required":["uri","terms"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Delete credential references from all consent terms

> Removes deleted credential URIs from any live consent terms that still reference them

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contract/consent/prune-deleted-uris":{"post":{"operationId":"contracts-deleteCredentialFromAllContracts","summary":"Delete credential references from all consent terms","description":"Removes deleted credential URIs from any live consent terms that still reference them","tags":["Contracts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"deletedUris":{"minItems":1,"type":"array","items":{"type":"string"}}},"required":["deletedUris"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"contractsUpdated":{"type":"number"},"removedSharedUris":{"type":"number"}},"required":["contractsUpdated","removedSharedUris"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Deletes Contract Terms

> Withdraws consent by deleting Contract Terms

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contract/consent/withdraw":{"delete":{"operationId":"contracts-withdrawConsent","summary":"Deletes Contract Terms","description":"Withdraws consent by deleting Contract Terms","tags":["Contracts"],"parameters":[{"in":"query","name":"uri","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Gets Transaction History

> Gets the transaction history for a set of Consent Flow Contract Terms

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contract/consent/history":{"post":{"operationId":"contracts-getTermsTransactionHistory","summary":"Gets Transaction History","description":"Gets the transaction history for a set of Consent Flow Contract Terms","tags":["Contracts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"sort":{"type":"string"},"uri":{"type":"string"},"query":{"default":{},"type":"object","properties":{"terms":{"type":"object","properties":{"read":{"type":"object","properties":{"anonymize":{"type":"boolean"},"credentials":{"type":"object","properties":{"shareAll":{"type":"boolean"},"sharing":{"type":"boolean"},"categories":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"sharing":{"type":"boolean"},"shared":{"type":"array","items":{"type":"string"}},"shareAll":{"type":"boolean"},"shareUntil":{"type":"string"}}}}}},"personal":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}}}},"write":{"type":"object","properties":{"credentials":{"type":"object","properties":{"categories":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}}},"personal":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}}}}},"action":{"anyOf":[{"type":"string","enum":["consent","update","sync","withdraw","write"]},{"type":"array","items":{"type":"string","enum":["consent","update","sync","withdraw","write"]}}]},"date":{"anyOf":[{"anyOf":[{"type":"object","properties":{"$gt":{"type":"string"}},"required":["$gt"]},{"type":"object","properties":{"$lt":{"type":"string"}},"required":["$lt"]}]},{"type":"object","properties":{"$eq":{"type":"string"}},"required":["$eq"]}]},"expiresAt":{"anyOf":[{"anyOf":[{"type":"object","properties":{"$gt":{"type":"string"}},"required":["$gt"]},{"type":"object","properties":{"$lt":{"type":"string"}},"required":["$lt"]}]},{"type":"object","properties":{"$eq":{"type":"string"}},"required":["$eq"]}]},"oneTime":{"type":"boolean"}}}},"required":["uri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"expiresAt":{"type":"string"},"oneTime":{"type":"boolean"},"terms":{"type":"object","properties":{"read":{"type":"object","properties":{"anonymize":{"type":"boolean"},"credentials":{"type":"object","properties":{"shareAll":{"type":"boolean"},"sharing":{"type":"boolean"},"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"sharing":{"type":"boolean"},"shared":{"type":"array","items":{"type":"string"}},"shareAll":{"type":"boolean"},"shareUntil":{"type":"string"}},"additionalProperties":false}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}}},"required":["credentials","personal"],"additionalProperties":false},"write":{"type":"object","properties":{"credentials":{"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}},"required":["credentials","personal"],"additionalProperties":false},"deniedWriters":{"type":"array","items":{"type":"string"}}},"required":["read","write"],"additionalProperties":false},"id":{"type":"string"},"action":{"type":"string","enum":["consent","update","sync","withdraw","write"]},"date":{"type":"string"},"uris":{"type":"array","items":{"type":"string"}}},"required":["id","action","date"],"additionalProperties":false}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Verifies that a profile has consented to a contract

> Checks if a profile has consented to the specified contract

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contract/verify":{"get":{"operationId":"contracts-verifyConsent","summary":"Verifies that a profile has consented to a contract","description":"Checks if a profile has consented to the specified contract","tags":["Contracts"],"parameters":[{"in":"query","name":"uri","schema":{"type":"string"},"required":true},{"in":"query","name":"profileId","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Sync credentials to a contract

> Syncs credentials to a contract that the profile has consented to

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contract/sync":{"post":{"operationId":"contracts-syncCredentialsToContract","summary":"Sync credentials to a contract","description":"Syncs credentials to a contract that the profile has consented to","tags":["Contracts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"termsUri":{"type":"string"},"categories":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"array","items":{"type":"string"}}}},"required":["termsUri","categories"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get credentials issued via a contract

> Gets all credentials that were issued via a contract

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contract/credentials":{"post":{"operationId":"contracts-getCredentialsForContract","summary":"Get credentials issued via a contract","description":"Gets all credentials that were issued via a contract","tags":["Contracts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"sort":{"type":"string"},"termsUri":{"type":"string"},"includeReceived":{"default":true,"type":"boolean"}},"required":["termsUri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"credentialUri":{"type":"string"},"termsUri":{"type":"string"},"contractUri":{"type":"string"},"boostUri":{"type":"string"},"category":{"type":"string"},"date":{"type":"string"}},"required":["credentialUri","termsUri","contractUri","boostUri","date"],"additionalProperties":false}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get all credentials written to any terms

> Gets all credentials that were written to any terms owned by this profile

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contracts/credentials":{"post":{"operationId":"contracts-getAllCredentialsForTerms","summary":"Get all credentials written to any terms","description":"Gets all credentials that were written to any terms owned by this profile","tags":["Contracts"],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"sort":{"type":"string"},"includeReceived":{"default":false,"type":"boolean"}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"credentialUri":{"type":"string"},"termsUri":{"type":"string"},"contractUri":{"type":"string"},"boostUri":{"type":"string"},"category":{"type":"string"},"date":{"type":"string"}},"required":["credentialUri","termsUri","contractUri","boostUri","date"],"additionalProperties":false}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Add autoboosts to a contract

> Adds one or more autoboost configurations to an existing consent flow contract. The caller must be the contract owner or a designated writer. The signing authority for each autoboost must be registered to the caller.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contracts/autoboosts/add":{"post":{"operationId":"contracts-addAutoBoostsToContract","summary":"Add autoboosts to a contract","description":"Adds one or more autoboost configurations to an existing consent flow contract. The caller must be the contract owner or a designated writer. The signing authority for each autoboost must be registered to the caller.","tags":["Contracts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contractUri":{"type":"string"},"autoboosts":{"type":"array","items":{"type":"object","properties":{"boostUri":{"type":"string"},"signingAuthority":{"type":"object","properties":{"endpoint":{"type":"string"},"name":{"type":"string"}},"required":["endpoint","name"]}},"required":["boostUri","signingAuthority"]}}},"required":["contractUri","autoboosts"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Remove autoboosts from a contract

> Removes one or more autoboosts from an existing consent flow contract, identified by their boost URIs. The caller must be the contract owner or a designated writer.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contracts/autoboosts/remove":{"post":{"operationId":"contracts-removeAutoBoostsFromContract","summary":"Remove autoboosts from a contract","description":"Removes one or more autoboosts from an existing consent flow contract, identified by their boost URIs. The caller must be the contract owner or a designated writer.","tags":["Contracts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contractUri":{"type":"string"},"boostUris":{"type":"array","items":{"type":"string"}}},"required":["contractUri","boostUris"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## AI Insights, consent flow notifcation request

> Sends the targeted user an AI insights consent flow request via a notification

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contracts/ai-insights/request":{"post":{"operationId":"contracts-sendAiInsightsContractRequest","summary":"AI Insights, consent flow notifcation request","description":"Sends the targeted user an AI insights consent flow request via a notification","tags":["Contracts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contractUri":{"type":"string"},"targetProfileId":{"type":"string"},"shareLink":{"type":"string"}},"required":["contractUri","targetProfileId","shareLink"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## AI Insights, consent flow share-notifcation request

> Sends the targeted user an AI insights share notification

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contracts/ai-insights/share-request":{"post":{"operationId":"contracts-sendAiInsightShareRequest","summary":"AI Insights, consent flow share-notifcation request","description":"Sends the targeted user an AI insights share notification","tags":["Contracts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"childProfileId":{"type":"string"},"targetProfileId":{"type":"string"},"shareLink":{"type":"string"}},"required":["targetProfileId","shareLink"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get requests sent for a given contract

> Gets a list of users and their request statuses for a given contract.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contracts/sent-requests":{"get":{"operationId":"contracts-getContractSentRequests","summary":"Get requests sent for a given contract","description":"Gets a list of users and their request statuses for a given contract.","tags":["Contracts"],"parameters":[{"in":"query","name":"contractUri","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"profile":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},"status":{"anyOf":[{"type":"string","enum":["pending","accepted","denied"]},{"type":"null"}]},"readStatus":{"anyOf":[{"type":"string","enum":["unseen","seen"]},{"type":"null"}]}},"required":["profile","status"],"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get request status for a specific profile under a contract

> Returns the request status and read status for a given profile in a specific contract.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contracts/request-status":{"get":{"operationId":"contracts-getRequestStatusForProfile","summary":"Get request status for a specific profile under a contract","description":"Returns the request status and read status for a given profile in a specific contract.","tags":["Contracts"],"parameters":[{"in":"query","name":"contractId","schema":{"anyOf":[{"type":"string"},{"type":"null"}]}},{"in":"query","name":"contractUri","schema":{"anyOf":[{"type":"string"},{"type":"null"}]}},{"in":"query","name":"targetProfileId","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"profile":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},"status":{"anyOf":[{"type":"string","enum":["pending","accepted","denied"]},{"type":"null"}]},"readStatus":{"anyOf":[{"type":"string","enum":["unseen","seen"]},{"type":"null"}]}},"required":["profile","status"],"additionalProperties":false},{"type":"null"}]}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Marks a contract request as seen

> Updates the read status of a contract request to "seen" for the specified target profile. Only contract writers are authorized to perform this action.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contracts/mark-request-as-seen":{"post":{"operationId":"contracts-markContractRequestAsSeen","summary":"Marks a contract request as seen","description":"Updates the read status of a contract request to \"seen\" for the specified target profile. Only contract writers are authorized to perform this action.","tags":["Contracts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contractUri":{"type":"string"},"targetProfileId":{"type":"string"}},"required":["contractUri","targetProfileId"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Cancels/removes a contract request

> Removes a REQUESTED\_FOR relationship, cancelling the request sent to the specified target profile. Only contract writers are authorized to perform this action.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contracts/cancel-request":{"post":{"operationId":"contracts-cancelContractRequest","summary":"Cancels/removes a contract request","description":"Removes a REQUESTED_FOR relationship, cancelling the request sent to the specified target profile. Only contract writers are authorized to perform this action.","tags":["Contracts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contractUri":{"type":"string"},"targetProfileId":{"type":"string"}},"required":["contractUri","targetProfileId"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get all contract requests for a target profile

> Gets all contract requests from all contracts for a specified target profile. Users can query their own requests.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contracts/all-requests-for-profile":{"get":{"operationId":"contracts-getAllContractRequestsForProfile","summary":"Get all contract requests for a target profile","description":"Gets all contract requests from all contracts for a specified target profile. Users can query their own requests.","tags":["Contracts"],"parameters":[{"in":"query","name":"targetProfileId","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"contract":{"type":"object","properties":{"read":{"type":"object","properties":{"anonymize":{"type":"boolean"},"credentials":{"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["credentials","personal"],"additionalProperties":false},"write":{"type":"object","properties":{"credentials":{"type":"object","properties":{"categories":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["categories"],"additionalProperties":false},"personal":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"required":{"type":"boolean"},"defaultEnabled":{"type":"boolean"}},"required":["required"],"additionalProperties":false}}},"required":["credentials","personal"],"additionalProperties":false},"uri":{"type":"string"}},"required":["read","write","uri"],"additionalProperties":false},"profile":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},"status":{"anyOf":[{"type":"string","enum":["pending","accepted","denied"]},{"type":"null"}]},"readStatus":{"anyOf":[{"type":"string","enum":["unseen","seen"]},{"type":"null"}]}},"required":["contract","profile","status"],"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get profiles a user has shared insights with

> Gets profiles with REQUESTED\_FOR relationships targeting the current user, including request status.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contracts/shared-insights-requests-for-profile":{"get":{"operationId":"contracts-getSharedInsightsRequestsForProfile","summary":"Get profiles a user has shared insights with","description":"Gets profiles with REQUESTED_FOR relationships targeting the current user, including request status.","tags":["Contracts"],"parameters":[{"in":"query","name":"targetProfileId","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"profile":{"type":"object","properties":{"profileId":{"type":"string","minLength":3,"maxLength":40,"description":"Unique, URL-safe identifier for the profile."},"displayName":{"default":"","description":"Human-readable display name for the profile.","type":"string"},"shortBio":{"default":"","description":"Short bio for the profile.","type":"string"},"bio":{"default":"","description":"Longer bio for the profile.","type":"string"},"did":{"type":"string","description":"Decentralized Identifier for the profile. (auto-assigned)"},"isPrivate":{"description":"Whether the profile is private or not and shows up in search results.","type":"boolean"},"profileVisibility":{"description":"Profile visibility: 'public', 'connections_only', or 'private'.","default":"public","type":"string","enum":["public","connections_only","private"]},"showEmail":{"description":"Whether to show email to connections.","default":false,"type":"boolean"},"allowConnectionRequests":{"description":"Who can send connection requests: 'anyone' or 'invite_only'.","default":"anyone","type":"string","enum":["anyone","invite_only"]},"email":{"description":"Contact email address for the profile. (deprecated)","type":"string"},"image":{"description":"Profile image URL for the profile.","type":"string"},"heroImage":{"description":"Hero image URL for the profile.","type":"string"},"websiteLink":{"description":"Website link for the profile.","type":"string"},"isServiceProfile":{"description":"Whether the profile is a service profile or not.","default":false,"type":"boolean"},"type":{"description":"Profile type: e.g. \"person\", \"organization\", \"service\".","type":"string"},"notificationsWebhook":{"description":"URL to send notifications to.","type":"string","format":"uri","pattern":"^http.*"},"display":{"description":"Display settings for the profile.","type":"object","properties":{"backgroundColor":{"type":"string"},"backgroundImage":{"type":"string"},"fadeBackgroundImage":{"type":"boolean"},"repeatBackgroundImage":{"type":"boolean"},"fontColor":{"type":"string"},"accentColor":{"type":"string"},"accentFontColor":{"type":"string"},"idBackgroundImage":{"type":"string"},"fadeIdBackgroundImage":{"type":"boolean"},"idBackgroundColor":{"type":"string"},"repeatIdBackgroundImage":{"type":"boolean"}},"additionalProperties":false},"highlightedCredentials":{"description":"Up to 5 unique boost URIs to highlight on the profile.","maxItems":5,"type":"array","items":{"type":"string"}},"role":{"description":"Role of the profile: e.g. \"teacher\", \"student\".","default":"","type":"string"},"dob":{"description":"Date of birth of the profile: e.g. \"1990-01-01\".","default":"","type":"string"},"country":{"description":"Country for the profile.","type":"string"},"locale":{"description":"BCP-47 language tag (e.g. 'es', 'fr', 'ar') — the user's preferred language for server-sent notifications and emails.","type":"string"},"approved":{"description":"Approval status for the profile.","type":"boolean"}},"required":["profileId","displayName","shortBio","bio","did"],"additionalProperties":false},"status":{"anyOf":[{"type":"string","enum":["pending","accepted","denied"]},{"type":"null"}]},"readStatus":{"anyOf":[{"type":"string","enum":["unseen","seen"]},{"type":"null"}]},"contractUri":{"type":"string"}},"required":["profile","status"],"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Forward a contract request

> Forwards a contract request to another profile

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Contracts"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/consent-flow-contracts/forward-request-to-profile":{"post":{"operationId":"contracts-forwardContractRequestToProfile","summary":"Forward a contract request","description":"Forwards a contract request to another profile","tags":["Contracts"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"parentProfileId":{"type":"string"},"targetProfileId":{"type":"string"},"contractUri":{"type":"string"}},"required":["parentProfileId","targetProfileId"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```


# DID Metadata

## Add Metadata to your did web

> Add Metadata to your did web

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"DID Metadata"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/did-metadata/create":{"post":{"operationId":"didMetadata-addDidMetadata","summary":"Add Metadata to your did web","description":"Add Metadata to your did web","tags":["DID Metadata"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"alsoKnownAs":{"type":"string"},"controller":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"verificationMethod":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"]},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"authentication":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"]},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"assertionMethod":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"]},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"keyAgreement":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"]},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"capabilityInvocation":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"]},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"capabilityDelegation":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"]},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"publicKey":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"]},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"service":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"serviceEndpoint":{"anyOf":[{},{"minItems":1,"type":"array","items":{}}]}},"required":["id","type","serviceEndpoint"],"additionalProperties":{}}},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"additionalProperties":{}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get DID Metadata

> Get DID Metadata

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"DID Metadata"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/did-metadata/{id}":{"get":{"operationId":"didMetadata-getDidMetadata","summary":"Get DID Metadata","description":"Get DID Metadata","tags":["DID Metadata"],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"alsoKnownAs":{"type":"string"},"controller":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"verificationMethod":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"],"additionalProperties":false},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"authentication":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"],"additionalProperties":false},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"assertionMethod":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"],"additionalProperties":false},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"keyAgreement":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"],"additionalProperties":false},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"capabilityInvocation":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"],"additionalProperties":false},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"capabilityDelegation":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"],"additionalProperties":false},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"publicKey":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"],"additionalProperties":false},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"service":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"serviceEndpoint":{"anyOf":[{},{"minItems":1,"type":"array","items":{}}]}},"required":["id","type","serviceEndpoint"],"additionalProperties":{}}},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"additionalProperties":{}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Delete DID Metadata

> Delete DID Metadata

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"DID Metadata"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/did-metadata/{id}":{"delete":{"operationId":"didMetadata-deleteDidMetadata","summary":"Delete DID Metadata","description":"Delete DID Metadata","tags":["DID Metadata"],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get My DID Metadata

> Get My DID Metadata

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"DID Metadata"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/did-metadata":{"post":{"operationId":"didMetadata-getMyDidMetadata","summary":"Get My DID Metadata","description":"Get My DID Metadata","tags":["DID Metadata"],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"alsoKnownAs":{"type":"string"},"controller":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"verificationMethod":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"],"additionalProperties":false},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"authentication":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"],"additionalProperties":false},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"assertionMethod":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"],"additionalProperties":false},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"keyAgreement":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"],"additionalProperties":false},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"capabilityInvocation":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"],"additionalProperties":false},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"capabilityDelegation":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"],"additionalProperties":false},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"publicKey":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"],"additionalProperties":false},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"service":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"serviceEndpoint":{"anyOf":[{},{"minItems":1,"type":"array","items":{}}]}},"required":["id","type","serviceEndpoint"],"additionalProperties":{}}},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["id"],"additionalProperties":{}}}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Update DID Metadata

> Update DID Metadata

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"DID Metadata"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/did-metadata/update/{id}":{"post":{"operationId":"didMetadata-updateDidMetadata","summary":"Update DID Metadata","description":"Update DID Metadata","tags":["DID Metadata"],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"updates":{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"alsoKnownAs":{"type":"string"},"controller":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"verificationMethod":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"]},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"authentication":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"]},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"assertionMethod":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"]},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"keyAgreement":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"]},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"capabilityInvocation":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"]},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"capabilityDelegation":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"]},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"publicKey":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"type":"string"},"controller":{"type":"string"},"publicKeyJwk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"required":["kty","crv","x"]},"publicKeyBase58":{"type":"string"},"publicKeyMultibase":{"type":"string"},"blockChainAccountId":{"type":"string"}},"required":["id","type","controller"],"additionalProperties":{}}]}},"service":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"serviceEndpoint":{"anyOf":[{},{"minItems":1,"type":"array","items":{}}]}},"required":["id","type","serviceEndpoint"],"additionalProperties":{}}},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"additionalProperties":{}}},"required":["updates"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```


# Claim Hooks

## Creates a claim hook

> This route creates a claim hook. Claim hooks are an atomic action that will be performed when a boost is claimed

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Claim Hooks"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/claim-hook/create":{"post":{"operationId":"claimHook-createClaimHook","summary":"Creates a claim hook","description":"This route creates a claim hook. Claim hooks are an atomic action that will be performed when a boost is claimed","tags":["Claim Hooks"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"hook":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"GRANT_PERMISSIONS"},"data":{"type":"object","properties":{"claimUri":{"type":"string"},"targetUri":{"type":"string"},"permissions":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}}}},"required":["claimUri","targetUri","permissions"]}},"required":["type","data"]},{"type":"object","properties":{"type":{"type":"string","const":"ADD_ADMIN"},"data":{"type":"object","properties":{"claimUri":{"type":"string"},"targetUri":{"type":"string"}},"required":["claimUri","targetUri"]}},"required":["type","data"]},{"type":"object","properties":{"type":{"type":"string","const":"AUTO_CONNECT"},"data":{"type":"object","properties":{"claimUri":{"type":"string"},"targetUri":{"type":"string"}},"required":["claimUri","targetUri"]}},"required":["type","data"]}],"type":"object"}},"required":["hook"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Gets Claim Hooks

> This route gets claim hooks attached to a given boost

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Claim Hooks"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/claim-hook/get":{"post":{"operationId":"claimHook-getClaimHooksForBoost","summary":"Gets Claim Hooks","description":"This route gets claim hooks attached to a given boost","tags":["Claim Hooks"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"sort":{"type":"string"},"query":{"type":"object","properties":{"type":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"data":{"type":"object","properties":{"claimUri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"targetUri":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"permissions":{"type":"object","properties":{"role":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"canView":{"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"canCreateChildren":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"canEditChildren":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"canRevokeChildren":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"canManageChildrenPermissions":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}}}}}},"required":["type","data"]},"uri":{"type":"string"}},"required":["uri"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"allOf":[{"type":"object","properties":{"id":{"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","createdAt","updatedAt"],"additionalProperties":false},{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"GRANT_PERMISSIONS"},"data":{"type":"object","properties":{"claimUri":{"type":"string"},"targetUri":{"type":"string"},"permissions":{"type":"object","properties":{"role":{"type":"string"},"canView":{"default":true,"type":"boolean"},"canEdit":{"type":"boolean"},"canIssue":{"type":"boolean"},"canRevoke":{"type":"boolean"},"canManagePermissions":{"type":"boolean"},"canIssueChildren":{"type":"string"},"canCreateChildren":{"type":"string"},"canEditChildren":{"type":"string"},"canRevokeChildren":{"type":"string"},"canManageChildrenPermissions":{"type":"string"},"canManageChildrenProfiles":{"type":"boolean"},"canViewAnalytics":{"type":"boolean"}},"additionalProperties":false}},"required":["claimUri","targetUri","permissions"],"additionalProperties":false}},"required":["type","data"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"ADD_ADMIN"},"data":{"type":"object","properties":{"claimUri":{"type":"string"},"targetUri":{"type":"string"}},"required":["claimUri","targetUri"],"additionalProperties":false}},"required":["type","data"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"AUTO_CONNECT"},"data":{"type":"object","properties":{"claimUri":{"type":"string"},"targetUri":{"type":"string"}},"required":["claimUri","targetUri"],"additionalProperties":false}},"required":["type","data"],"additionalProperties":false}],"type":"object"}]}}},"required":["hasMore","records"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Delete a Claim Hook

> This route deletes a claim hook

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Claim Hooks"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/claim-hook/update":{"post":{"operationId":"claimHook-deleteClaimHook","summary":"Delete a Claim Hook","description":"This route deletes a claim hook","tags":["Claim Hooks"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```


# Auth Grants

## Add AuthGrant to your profile

> Add AuthGrant to your profile

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Auth Grants"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/auth-grant/create":{"post":{"operationId":"authGrants-addAuthGrant","summary":"Add AuthGrant to your profile","description":"Add AuthGrant to your profile","tags":["Auth Grants"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"scope":{"type":"string"},"expiresAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get AuthGrant

> Get AuthGrant

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Auth Grants"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/auth-grant/{id}":{"get":{"operationId":"authGrants-getAuthGrant","summary":"Get AuthGrant","description":"Get AuthGrant","tags":["Auth Grants"],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"challenge":{"type":"string","minLength":10,"maxLength":100,"pattern":"^auth-grant:.*"},"status":{"type":"string","enum":["revoked","active"]},"scope":{"type":"string"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"expiresAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]}},"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Delete AuthGrant

> Delete AuthGrant

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Auth Grants"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/auth-grant/{id}":{"delete":{"operationId":"authGrants-deleteAuthGrant","summary":"Delete AuthGrant","description":"Delete AuthGrant","tags":["Auth Grants"],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get My AuthGrants

> Get My AuthGrants

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Auth Grants"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/profile/auth-grants":{"post":{"operationId":"authGrants-getAuthGrants","summary":"Get My AuthGrants","description":"Get My AuthGrants","tags":["Auth Grants"],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"type":"number"},"cursor":{"type":"string"},"query":{"type":"object","properties":{"id":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"name":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"description":{"anyOf":[{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]},{"type":"object","properties":{"$or":{"type":"array","items":{"anyOf":[{"anyOf":[{"type":"string"},{"type":"object","properties":{"$in":{"type":"array","items":{"type":"string"}}},"required":["$in"]}]},{"type":"object","properties":{"$regex":{"type":"string"}}}]}}},"required":["$or"]}]},"status":{"type":"string","enum":["active","revoked"]}}}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"challenge":{"type":"string","minLength":10,"maxLength":100,"pattern":"^auth-grant:.*"},"status":{"type":"string","enum":["revoked","active"]},"scope":{"type":"string"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"expiresAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]}},"required":["id","name","challenge","status","scope","createdAt"],"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Update AuthGrant

> Update AuthGrant

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Auth Grants"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/auth-grant/update/{id}":{"post":{"operationId":"authGrants-updateAuthGrant","summary":"Update AuthGrant","description":"Update AuthGrant","tags":["Auth Grants"],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"updates":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"}}}},"required":["updates"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Revoke AuthGrant

> Revoke AuthGrant

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Auth Grants"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/auth-grant/{id}/revoke":{"post":{"operationId":"authGrants-revokeAuthGrant","summary":"Revoke AuthGrant","description":"Revoke AuthGrant","tags":["Auth Grants"],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```


# Utilities

## Check health of endpoint

> Check if the endpoint is healthy and well

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Utilities"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/health-check":{"get":{"operationId":"utilities-healthCheck","summary":"Check health of endpoint","description":"Check if the endpoint is healthy and well","tags":["Utilities"],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Deep health check (exercises DIDKit end to end)

> Issues and verifies a test credential + presentation with the service keypair, proving the full DIDKit crypto path (plugin load, signing, and the runtime delegation inside the native plugin) works. Reports which DIDKit engine loaded. Added after the 2026-07-02 incidents, where shallow health checks stayed green while DIDKit paths were broken.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Utilities"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/health-check/deep":{"get":{"operationId":"utilities-deepHealthCheck","summary":"Deep health check (exercises DIDKit end to end)","description":"Issues and verifies a test credential + presentation with the service keypair, proving the full DIDKit crypto path (plugin load, signing, and the runtime delegation inside the native plugin) works. Reports which DIDKit engine loaded. Added after the 2026-07-02 incidents, where shallow health checks stayed green while DIDKit paths were broken.","tags":["Utilities"],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"healthy":{"type":"boolean"},"version":{"type":"string"},"didkitEngine":{"type":"string","enum":["native","wasm","unloaded"]},"did":{"type":"string"},"vpVerified":{"type":"boolean"},"verificationErrors":{"type":"array","items":{"type":"string"}},"ms":{"type":"number"}},"required":["healthy","version","didkitEngine","did","vpVerified","verificationErrors","ms"],"additionalProperties":false}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Resolve a recipient locale by email

> Returns the account's saved BCP-47 locale preference for this email, or \`null\` when no preference is set or no account exists. Returning \`null\` (rather than defaulting to 'en') lets the caller fall back to its own signal (e.g. the client UI locale) before English. Never reveals whether an account exists — a missing account and an account with no saved locale both return \`null\` — so it is safe to call before authentication (e.g. to localize a login-code email). Rate limited to 10 lookups per email per hour (plus a broad per-IP circuit breaker); callers should treat any error as an unresolved locale and fall back, never as a failure.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Utilities"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/utilities/resolve-email-locale":{"post":{"operationId":"utilities-resolveEmailLocale","summary":"Resolve a recipient locale by email","description":"Returns the account's saved BCP-47 locale preference for this email, or `null` when no preference is set or no account exists. Returning `null` (rather than defaulting to 'en') lets the caller fall back to its own signal (e.g. the client UI locale) before English. Never reveals whether an account exists — a missing account and an account with no saved locale both return `null` — so it is safe to call before authentication (e.g. to localize a login-code email). Rate limited to 10 lookups per email per hour (plus a broad per-IP circuit breaker); callers should treat any error as an unresolved locale and fall back, never as a failure.","tags":["Utilities"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"required":["email"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"locale":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["locale"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Request a list of valid challenges

> Generates an arbitrary number of valid challenges for a did, then returns them

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Utilities"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/challenges":{"get":{"operationId":"utilities-getChallenges","summary":"Request a list of valid challenges","description":"Generates an arbitrary number of valid challenges for a did, then returns them","tags":["Utilities"],"parameters":[{"in":"query","name":"amount","schema":{"default":100,"type":"integer","exclusiveMinimum":0,"maximum":100}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get LCN Did

> Gets the did:web for the LCN itself

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"tags":[{"name":"Utilities"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/did":{"get":{"operationId":"utilities-getDid","summary":"Get LCN Did","description":"Gets the did:web for the LCN itself","tags":["Utilities"],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```


# Models

## The \_\_schema0 object

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"components":{"schemas":{"__schema0":{"type":"object","properties":{"id":{"type":"string"},"statement":{"type":"string"},"description":{"type":"string"},"code":{"type":"string"},"icon":{"type":"string"},"type":{"type":"string"},"status":{"type":"string","enum":["active","archived"]},"children":{"type":"array","items":{"$ref":"#/components/schemas/__schema0"}}},"required":["statement"]}}}}
```

## The error.BAD\_REQUEST object

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"components":{"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"}}}}
```

## The error.UNAUTHORIZED object

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"components":{"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"}}}}
```

## The error.FORBIDDEN object

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"components":{"schemas":{"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"}}}}
```

## The error.NOT\_FOUND object

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"components":{"schemas":{"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"}}}}
```

## The error.INTERNAL\_SERVER\_ERROR object

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"components":{"schemas":{"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}}}
```

## The \_\_schema1 object

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Network API","version":"1.0.0"},"components":{"schemas":{"__schema1":{"type":"object","properties":{"id":{"type":"string"},"statement":{"type":"string"},"description":{"type":"string"},"code":{"type":"string"},"icon":{"type":"string"},"type":{"default":"skill","type":"string"},"status":{"default":"active","type":"string","enum":["active","archived"]},"frameworkId":{"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"children":{"type":"array","items":{"$ref":"#/components/schemas/__schema1"}},"hasChildren":{"type":"boolean"},"childrenCursor":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","statement","type","status","children","hasChildren"],"additionalProperties":false}}}}
```


# LearnCloud Storage API

**LearnCloud** **Storage** is LearnCard’s end-to-end encrypted storage system:

* Store credentials and presentations securely
* Sync across devices
* Swap in your own storage backend if desired

#### Key Features <a href="#key-features" id="key-features"></a>

* **xAPI Integration**: Track and store learning experiences
* **OIDC Authentication**: Provide OpenID Connect authentication
* **DID Web Resolution**: Resolve DIDs for identity management
* **End-to-End Encryption:** Data is encrypted client-side.

<br>


# Authentication

### Authenticating with the LearnCloud Storage API

To interact with the LearnCloud Storage API, you can choose one of two ways to authenticate:

1. Using the LearnCloud Storage Plugin (`@learncard/learn-cloud-plugin`) which handles authentication for you. **(Preferred option)**
2. Directly through the API endpoints using challenge-based DID Authentication. (most complex)

#### 1. Using LearnCloud Storage Plugin

To authenticate using the LearnCloud Storage Plugin (`@learncard/learn-cloud-plugin`), first install the package:

```bash
bun add @learncard/learn-cloud-plugin
```

Then, either instantiate a LearnCloud STorage enabled LearnCard, or add the Storage Plugin to an existing LearnCard:

{% tabs %}
{% tab title="Direct Instantiation" %}
{% code lineNumbers="true" %}

```typescript
import { initLearnCard } from '@learncard/init';
import didkit from '@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm?url';

const storageLearnCard = await initLearnCard({
    seed,
    network: true,
    didkit,
});
```

{% endcode %}
{% endtab %}

{% tab title="Add Plugin" %}

```typescript
import { initLearnCard } from '@learncard/init';
import { getLearnCloudPlugin } from '@learncard/learn-cloud-plugin';
import didkit from '@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm?url';

const storageAPI = 'https://cloud.learncard.com/trpc';

const learnCard = await initLearnCard({
    seed,
    didkit,
});

const storageLearnCard = await learnCard.addPlugin(
    await getLearnCloudPlugin(learnCard, storageAPI)
);
```

{% endtab %}
{% endtabs %}

When using the LearnCloud Storage Plugin, challenge-based DID Authentication is handled for you, so no further steps are necessary.

#### 2. Using Challenge-based DID Authentication

Storage API uses DID-based authentication with a challenge-response mechanism and scope-based authorization.

**Simple High-Level Auth Flow:**

```mermaid
sequenceDiagram
    participant Client as Client
    participant Network as Storage API
          Client->>Network: Request access
          Network->>Client: Provide challenge
          Client->>Client: Sign challenge with DID
          Client->>Network: Submit signed challenge
          Network->>Network: Verify signature
          Network->>Client: Grant authenticated access
```

**Granular Auth Flow:**

```mermaid
sequenceDiagram
    participant Client
    participant BrainService as "Storage API"
    participant Cache as "Redis Cache"

    Client->>BrainService: utilities.getChallenges()
    BrainService-->>Client: [challenge1, challenge2, ...]

    Client->>Client: Sign challenge with DID
    Client->>BrainService: Request with signed challenge in Authorization header

    BrainService->>BrainService: Extract DID from JWT
    BrainService->>Cache: Check if challenge is valid for DID
    Cache-->>BrainService: Validation result

    alt Valid challenge
        BrainService->>Cache: Invalidate challenge
        BrainService->>BrainService: Process request
        BrainService-->>Client: Response
    else Invalid challenge
        BrainService-->>Client: 401 Unauthorized
    end
```

If you choose to use the API endpoints directly, you'll need to manage challenge-based DID Authentication for each request. Here's a simplified TypeScript example to help you implement this authentication method:

```typescript

async function getClient(
  url = 'https://cloud.learncard.com/api': string,
  didAuthFunction: (challenge?: string) => Promise<string>
) {
  let challenges: string[] = [];

  const getChallenges = async (amount = 95 + Math.round((Math.random() - 0.5) * 5)): Promise<string[]> => {
    // Call the API to get a list of challenges
    // Replace this line with your preferred way of making API calls
    const response = await fetch(url + "/challenges?amount=" + amount);
    return await response.json();
  };

  challenges = await getChallenges();

  async function getAuthHeaders() {
    if (challenges.length === 0) challenges.push(...(await getChallenges()));
    return { Authorization: `Bearer ${await didAuthFunction(challenges.pop())}` };
  }

  // Use getAuthHeaders in your API calls to set the Authorization header
}

export default getClient;
```

In this example, we first define a `getClient` function that takes a `url` and a `didAuthFunction`. The `didAuthFunction` should be an asynchronous function that returns a signed challenge as a string.

The `getChallenges` function fetches a list of challenges from the API. The `getAuthHeaders` function generates an Authorization header using the `didAuthFunction` and a challenge. This header can then be used in your API calls.

## Authorization

#### Route Middleware <a href="#route-middleware" id="route-middleware"></a>

The system uses several middleware layers for authentication and authorization:

1. `openRoute`: Base middleware that allows public access
2. `didRoute`: Requires a valid DID in the request
3. `didAndChallengeRoute`: Requires a valid DID and challenge
4. `profileRoute`: Requires a valid DID, challenge, and existing profile


# 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

```http
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

```json
"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

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

#### Response

```json
{
  "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

```json
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

```json
[
  {
    "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)

```ts
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.)

```ts
{
  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

```http
POST /index/get
Authorization: Bearer <JWT>
Content-Type: application/json
```

```json
{
  "limit": 25,
  "query": { "type": "EducationCredential" },
  "encrypt": true,
  "sort": "newestFirst",
  "includeAssociatedDids": true
}
```

#### Response

```json
{
  "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

```http
POST /index/count
Authorization: Bearer <JWT>
Content-Type: application/json
```

```json
{
  "query": { "type": "EducationCredential" },
  "encrypt": true,
  "includeAssociatedDids": true
}
```

#### Response

```json
42
```

or

```json
{ /* 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

```http
POST /index/add
Authorization: Bearer <JWT>
Content-Type: application/json
```

```json
{
  "record": { /* JWE object + fields: z.string().array() */ }
}
```

#### Response

```json
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

```http
POST /index/addMany
Authorization: Bearer <JWT>
Content-Type: application/json
```

```json
{
  "records": [
    {
      /* JWE object */
    },
    {
      /* JWE object */
    }
  ]
}
```

#### Response

```json
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

```http
PATCH /index/abc123
Authorization: Bearer <JWT>
Content-Type: application/json
```

```json
{
  "id": "abc123",
  "updates": {
    /* JWE object updates */
  }
}
```

#### Response

```json
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

```http
DELETE /index/abc123
Authorization: Bearer <JWT>
```

#### Response

```json
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

```http
DELETE /index
Authorization: Bearer <JWT>
```

#### Response

```json
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

```http
POST /xapi/statements
Authorization: Bearer <JWT>
X-Experience-API-Version: 1.0.3
X-VP: <JWT>
Content-Type: application/json
```

```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

```http
GET /xapi/statements?agent={actor}
Authorization: Bearer <JWT>
X-Experience-API-Version: 1.0.3
X-VP: <JWT>
```

```ts
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

```http
GET /xapi/statements?agent={...}&verb={...}&since={...}
```

```ts
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

```ts
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**

```ts
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.

```ts
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


# Architecture

## Deployment Architecture <a href="#deployment-architecture" id="deployment-architecture"></a>

LearnCloud Storage is deployed as a serverless application on AWS.

```mermaid
graph TB
    subgraph "AWS Infrastructure"
        api["API Gateway"]
        lambda["Lambda Functions"]
        sg["Security Groups"]
        vpc["Virtual Private Cloud"]

        subgraph "Storage"
            mongodb["MongoDB"]
            redis["Redis Cache"]
        end
    end

    client["Client Applications"] -->|"HTTP/HTTPS"| api
    api -->|"Invokes"| lambda
    lambda -->|"Query/Update"| mongodb
    lambda -->|"Cache"| redis

    lambda -->|"Handle"| xapi["xAPI Statements"]
    lambda -->|"Authenticate"| oidc["OIDC Flows"]
```

## API Endpoints

The LearnCloud Storage API exposes the following endpoints:

| Endpoint                            | Description                 | Handler            |
| ----------------------------------- | --------------------------- | ------------------ |
| `/trpc/*`                           | tRPC API endpoints          | `trpcHandler`      |
| `/api/*`                            | OpenAPI REST endpoints      | `openApiHandler`   |
| `/users/*`                          | DID Web identity resolution | `didWebHandler`    |
| `/xapi/*`                           | xAPI statement handling     | `xApiHandler`      |
| `/oidc/*`                           | OIDC authentication         | `oidcHandler`      |
| `/.well-known/openid-configuration` | OIDC configuration          | `oidcHandler`      |
| `/docs`                             | Swagger UI documentation    | `swaggerUiHandler` |


# Storage

## Store a Credential/Presentation

> This endpoint stores a credential/presentation, returning a uri that can be used to resolve it

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"Storage"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/storage/store":{"post":{"operationId":"storage-store","summary":"Store a Credential/Presentation","description":"This endpoint stores a credential/presentation, returning a uri that can be used to resolve it","tags":["Storage"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"item":{"anyOf":[{"anyOf":[{"anyOf":[{"anyOf":[{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]}},"required":["@context","type","issuer","credentialSubject"],"additionalProperties":{}},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}}]},{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"verifiableCredential":{"anyOf":[{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}}}]},"holder":{"type":"string"},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","proof"],"additionalProperties":{}}]},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]}]},{"type":"object","properties":{"format":{"type":"string","enum":["w3c-vc-2.0","w3c-vc-1.1","jwt-vc-json","dc+sd-jwt","vc+sd-jwt","mso_mdoc"]},"data":{"type":"string"}},"required":["format","data"],"additionalProperties":{}}]}},"required":["item"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Delete a stored Credential/Presentation

> Deletes a stored item by URI when the caller is authorized.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"Storage"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/storage/delete":{"delete":{"operationId":"storage-delete","summary":"Delete a stored Credential/Presentation","description":"Deletes a stored item by URI when the caller is authorized.","tags":["Storage"],"parameters":[{"in":"query","name":"uri","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Resolves a URI to a Credential/Presentation

> This endpoint stores a credential/presentation, returning a uri that can be used to resolve it

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"Storage"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/storage/resolve":{"get":{"operationId":"storage-resolve","summary":"Resolves a URI to a Credential/Presentation","description":"This endpoint stores a credential/presentation, returning a uri that can be used to resolve it","tags":["Storage"],"parameters":[{"in":"query","name":"uri","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"additionalProperties":false},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"],"additionalProperties":false},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"],"additionalProperties":false}}},"required":["protected","iv","ciphertext","tag"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Resolves URIs to Credentials/Presentations

> This endpoint resolves a batch or URIs

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"Storage"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/storage/resolve/batch":{"post":{"operationId":"storage-batchResolve","summary":"Resolves URIs to Credentials/Presentations","description":"This endpoint resolves a batch or URIs","tags":["Storage"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uris":{"type":"array","items":{"type":"string"}}},"required":["uris"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"anyOf":[{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"additionalProperties":false},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"],"additionalProperties":false},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"],"additionalProperties":false}}},"required":["protected","iv","ciphertext","tag"],"additionalProperties":false},{"type":"null"}]}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```


# Index

## Get CredentialRecords index

> This endpoint allows the user to query their CredentialRecords index

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"Index"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/index/get":{"post":{"operationId":"index-get","summary":"Get CredentialRecords index","description":"This endpoint allows the user to query their CredentialRecords index","tags":["Index"],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"query":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]}]},"encrypt":{"default":true,"type":"boolean"},"sort":{"default":"newestFirst","type":"string","enum":["newestFirst","oldestFirst"]},"includeAssociatedDids":{"default":true,"type":"boolean"}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"encryptedRecord":{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"additionalProperties":false},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"],"additionalProperties":false},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"],"additionalProperties":false}}},"required":["protected","iv","ciphertext","tag"],"additionalProperties":false},"fields":{"type":"array","items":{"type":"string"}},"id":{"type":"string"}},"required":["encryptedRecord","fields","id"],"additionalProperties":{}}}},"required":["hasMore","records"],"additionalProperties":false},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"additionalProperties":false},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"],"additionalProperties":false},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"],"additionalProperties":false}}},"required":["protected","iv","ciphertext","tag"],"additionalProperties":false}]}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Count records in index for query

> This endpoint allows the user to see how many records they have in their index for a query

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"Index"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/index/count":{"post":{"operationId":"index-count","summary":"Count records in index for query","description":"This endpoint allows the user to see how many records they have in their index for a query","tags":["Index"],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]}]},"encrypt":{"default":true,"type":"boolean"},"includeAssociatedDids":{"default":true,"type":"boolean"}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"anyOf":[{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"additionalProperties":false},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"],"additionalProperties":false},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"],"additionalProperties":false}}},"required":["protected","iv","ciphertext","tag"],"additionalProperties":false}]}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Add to index

> This endpoint adds a CredentialRecord to the user's index

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"Index"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/index/add":{"post":{"operationId":"index-add","summary":"Add to index","description":"This endpoint adds a CredentialRecord to the user's index","tags":["Index"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"record":{"anyOf":[{"type":"object","properties":{"encryptedRecord":{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]},"fields":{"type":"array","items":{"type":"string"}}},"required":["encryptedRecord","fields"],"additionalProperties":{}},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]}]}},"required":["record"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Add many to index

> This endpoint adds multiple CredentialRecords to the user's index

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"Index"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/index/addMany":{"post":{"operationId":"index-addMany","summary":"Add many to index","description":"This endpoint adds multiple CredentialRecords to the user's index","tags":["Index"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"records":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"encryptedRecord":{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]},"fields":{"type":"array","items":{"type":"string"}}},"required":["encryptedRecord","fields"],"additionalProperties":{}}},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]}]}},"required":["records"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Deletes a CredentialRecord

> This endpoint deletes a CredentialRecord

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"Index"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/index/{id}":{"delete":{"operationId":"index-remove","summary":"Deletes a CredentialRecord","description":"This endpoint deletes a CredentialRecord","tags":["Index"],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Update a CredentialRecord

> This endpoint updates a CredentialRecord

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"Index"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/index/{id}":{"patch":{"operationId":"index-update","summary":"Update a CredentialRecord","description":"This endpoint updates a CredentialRecord","tags":["Index"],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"updates":{"anyOf":[{"type":"object","properties":{"encryptedRecord":{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}}},"encrypted_key":{"type":"string"}}}}}},"fields":{"type":"array","items":{"type":"string"}},"id":{"type":"string"}},"additionalProperties":{}},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]}]}},"required":["updates"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Deletes all CredentialRecords

> This endpoint deletes all CredentialRecords

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"Index"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/index":{"delete":{"operationId":"index-removeAll","summary":"Deletes all CredentialRecords","description":"This endpoint deletes all CredentialRecords","tags":["Index"],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```


# User

## Get dids associated with this user

> Gets all dids that have been associated with the user for this did

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"User"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/user/dids":{"get":{"operationId":"user-getDids","summary":"Get dids associated with this user","description":"Gets all dids that have been associated with the user for this did","tags":["User"],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Associate a did with this user

> Associates a did with the user for this did

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"User"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/user/dids/add":{"post":{"operationId":"user-addDid","summary":"Associate a did with this user","description":"Associates a did with the user for this did","tags":["User"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"presentation":{"anyOf":[{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"verifiableCredential":{"anyOf":[{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}}}]},"holder":{"type":"string"},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","proof"],"additionalProperties":{}},{"type":"string"}]}},"required":["presentation"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Disassociate a did with this user

> Disassociates a did with the user for this did

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"User"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/user/dids/remove":{"post":{"operationId":"user-removeDid","summary":"Disassociate a did with this user","description":"Disassociates a did with the user for this did","tags":["User"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"presentation":{"anyOf":[{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"verifiableCredential":{"anyOf":[{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}}}]},"holder":{"type":"string"},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","proof"],"additionalProperties":{}},{"type":"string"}]}},"required":["presentation"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Sets the primary did for a user

> Sets the primary did for the user with this did

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"User"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/user/dids/setPrimary":{"post":{"operationId":"user-setPrimaryDid","summary":"Sets the primary did for a user","description":"Sets the primary did for the user with this did","tags":["User"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"presentation":{"anyOf":[{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"verifiableCredential":{"anyOf":[{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"@context":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}},"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"issuer":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"anyOf":[{"type":"string"},{"minItems":1,"type":"array","items":{"type":"string"}}]},"name":{"type":"string"},"url":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"endorsement":{"type":"array","items":{}},"image":{"anyOf":[{"type":"string"},{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"caption":{"type":"string"}},"required":["id","type"]}]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"address":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"addressCountry":{"type":"string"},"addressCountryCode":{"type":"string"},"addressRegion":{"type":"string"},"addressLocality":{"type":"string"},"streetAddress":{"type":"string"},"postOfficeBoxNumber":{"type":"string"},"postalCode":{"type":"string"},"geo":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["type","latitude","longitude"]}},"required":["type"]},"otherIdentifier":{"type":"array","items":{"type":"object","properties":{"type":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"type":"array","items":{"type":"string"}}]},"identifier":{"type":"string"},"identifierType":{"anyOf":[{"type":"string","enum":["sourcedId","systemId","productId","userName","accountId","emailAddress","nationalIdentityNumber","isbn","issn","lisSourcedId","oneRosterSourcedId","sisSourcedId","ltiContextId","ltiDeploymentId","ltiToolId","ltiPlatformId","ltiUserId","identifier"]},{"type":"string"}]}},"required":["type","identifier","identifierType"]}},"official":{"type":"string"},"parentOrg":{},"familyName":{"type":"string"},"givenName":{"type":"string"},"additionalName":{"type":"string"},"patronymicName":{"type":"string"},"honorificPrefix":{"type":"string"},"honorificSuffix":{"type":"string"},"familyNamePrefix":{"type":"string"},"dateOfBirth":{"type":"string"}},"additionalProperties":{}}]},"credentialSubject":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"}},"additionalProperties":{}}}]},"refreshService":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"credentialSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"],"additionalProperties":{}}}]},"issuanceDate":{"type":"string"},"expirationDate":{"type":"string"},"credentialStatus":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"name":{"type":"string"},"description":{"type":"string"},"validFrom":{"type":"string"},"validUntil":{"type":"string"},"status":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"termsOfUse":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"evidence":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"minItems":1,"type":"array","items":{"type":"string"}},"name":{"type":"string"},"narrative":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"}},"required":["type"],"additionalProperties":{}}}]},"renderMethod":{"anyOf":[{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]},{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"TemplateRenderMethod"},"renderSuite":{"type":"string"},"template":{"type":"string"},"renderProperty":{"type":"array","items":{"type":"string"}},"outputPreference":{"type":"object","properties":{"mediaType":{"type":"string"}},"required":["mediaType"]}},"required":["type","renderSuite","template"]},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}]},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","issuer","credentialSubject","proof"],"additionalProperties":{}}}]},"holder":{"type":"string"},"proof":{"anyOf":[{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}},{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"created":{"type":"string"},"challenge":{"type":"string"},"domain":{"type":"string"},"nonce":{"type":"string"},"proofPurpose":{"type":"string"},"verificationMethod":{"type":"string"},"jws":{"type":"string"}},"required":["type","created","proofPurpose","verificationMethod"],"additionalProperties":{}}}]}},"required":["@context","type","proof"],"additionalProperties":{}},{"type":"string"}]}},"required":["presentation"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```


# Custom Storage

## Create a document in custom storage

> This endpoint allows the user to create a document in their custom store.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"Custom Storage"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/custom-storage/create":{"post":{"operationId":"customStorage-create","summary":"Create a document in custom storage","description":"This endpoint allows the user to create a document in their custom store.","tags":["Custom Storage"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"item":{"anyOf":[{"type":"object","properties":{"encryptedRecord":{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]},"fields":{"type":"array","items":{"type":"string"}}},"required":["encryptedRecord","fields"],"additionalProperties":{}},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]}]}},"required":["item"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Create a document in custom storage

> This endpoint allows the user to create a document in their custom store.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"Custom Storage"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/custom-storage/create-many":{"post":{"operationId":"customStorage-createMany","summary":"Create a document in custom storage","description":"This endpoint allows the user to create a document in their custom store.","tags":["Custom Storage"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"encryptedRecord":{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]},"fields":{"type":"array","items":{"type":"string"}}},"required":["encryptedRecord","fields"],"additionalProperties":{}}},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]}]}},"required":["items"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"boolean"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Read custom documents from storage

> This endpoint allows the user to query for documents in their custom store.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"Custom Storage"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/custom-storage/read":{"post":{"operationId":"customStorage-read","summary":"Read custom documents from storage","description":"This endpoint allows the user to query for documents in their custom store.","tags":["Custom Storage"],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"default":25,"type":"number"},"cursor":{"type":"string"},"query":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]}]},"encrypt":{"default":true,"type":"boolean"},"includeAssociatedDids":{"default":true,"type":"boolean"}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"cursor":{"type":"string"},"hasMore":{"type":"boolean"},"records":{"type":"array","items":{"type":"object","properties":{"encryptedRecord":{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"additionalProperties":false},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"],"additionalProperties":false},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"],"additionalProperties":false}}},"required":["protected","iv","ciphertext","tag"],"additionalProperties":false},"fields":{"type":"array","items":{"type":"string"}}},"required":["encryptedRecord","fields"],"additionalProperties":{}}}},"required":["hasMore","records"],"additionalProperties":false},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}},"additionalProperties":false},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"],"additionalProperties":false},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"],"additionalProperties":false}}},"required":["protected","iv","ciphertext","tag"],"additionalProperties":false}]}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Count custom documents from storage

> This endpoint allows the user to count documents in their custom store.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"Custom Storage"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/custom-storage/count":{"post":{"operationId":"customStorage-count","summary":"Count custom documents from storage","description":"This endpoint allows the user to count documents in their custom store.","tags":["Custom Storage"],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]}]},"includeAssociatedDids":{"default":true,"type":"boolean"}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"number"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Update custom documents in storage

> This endpoint allows the user to update documents in their custom store.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"Custom Storage"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/custom-storage/update":{"post":{"operationId":"customStorage-update","summary":"Update custom documents in storage","description":"This endpoint allows the user to update documents in their custom store.","tags":["Custom Storage"],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]}]},"update":{"anyOf":[{"type":"object","properties":{"encryptedRecord":{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]},"fields":{"type":"array","items":{"type":"string"}}},"additionalProperties":{}},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]}]},"includeAssociatedDids":{"default":true,"type":"boolean"}},"required":["update"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"number"}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Delete custom documents in storage

> This endpoint allows the user to delete documents in their custom store.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"Custom Storage"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/custom-storage/delete":{"post":{"operationId":"customStorage-delete","summary":"Delete custom documents in storage","description":"This endpoint allows the user to delete documents in their custom store.","tags":["Custom Storage"],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"object","properties":{"protected":{"type":"string"},"iv":{"type":"string"},"ciphertext":{"type":"string"},"tag":{"type":"string"},"aad":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","properties":{"header":{"type":"object","properties":{"alg":{"type":"string"},"iv":{"type":"string"},"tag":{"type":"string"},"epk":{"type":"object","properties":{"kty":{"type":"string"},"crv":{"type":"string"},"x":{"type":"string"},"y":{"type":"string"},"n":{"type":"string"},"d":{"type":"string"}}},"kid":{"type":"string"},"apv":{"type":"string"},"apu":{"type":"string"}},"required":["alg","iv","tag"]},"encrypted_key":{"type":"string"}},"required":["header","encrypted_key"]}}},"required":["protected","iv","ciphertext","tag"]}]},"includeAssociatedDids":{"default":true,"type":"boolean"}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"anyOf":[{"type":"number"},{"type":"boolean","const":false}]}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```


# Utilities

## Check health of endpoint

> Check if the endpoint is healthy and well

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"Utilities"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/health-check":{"get":{"operationId":"utilities-healthCheck","summary":"Check health of endpoint","description":"Check if the endpoint is healthy and well","tags":["Utilities"],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Deep health check (exercises DIDKit end to end)

> Issues and verifies a test credential + presentation with the service keypair, proving the full DIDKit crypto path (plugin load, signing, and the runtime delegation inside the native plugin) works. Reports which DIDKit engine loaded. Added after the 2026-07-02 incidents, where shallow health checks stayed green while DIDKit paths were broken.

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"Utilities"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/health-check/deep":{"get":{"operationId":"utilities-deepHealthCheck","summary":"Deep health check (exercises DIDKit end to end)","description":"Issues and verifies a test credential + presentation with the service keypair, proving the full DIDKit crypto path (plugin load, signing, and the runtime delegation inside the native plugin) works. Reports which DIDKit engine loaded. Added after the 2026-07-02 incidents, where shallow health checks stayed green while DIDKit paths were broken.","tags":["Utilities"],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"healthy":{"type":"boolean"},"version":{"type":"string"},"didkitEngine":{"type":"string","enum":["native","wasm","unloaded"]},"did":{"type":"string"},"vpVerified":{"type":"boolean"},"verificationErrors":{"type":"array","items":{"type":"string"}},"ms":{"type":"number"}},"required":["healthy","version","didkitEngine","did","vpVerified","verificationErrors","ms"],"additionalProperties":false}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Request a list of valid challenges

> Generates an arbitrary number of valid challenges for a did, then returns them

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"Utilities"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/challenges":{"get":{"operationId":"utilities-getChallenges","summary":"Request a list of valid challenges","description":"Generates an arbitrary number of valid challenges for a did, then returns them","tags":["Utilities"],"parameters":[{"in":"query","name":"amount","schema":{"default":100,"type":"integer","exclusiveMinimum":0,"maximum":100}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```

## Get LCN Did

> Gets the did:web for the LearnCloud itself

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"tags":[{"name":"Utilities"}],"servers":[{"url":"/api"}],"security":[{"Authorization":[]}],"components":{"securitySchemes":{"Authorization":{"type":"http","scheme":"bearer"}},"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}},"paths":{"/did":{"get":{"operationId":"utilities-getDid","summary":"Get LCN Did","description":"Gets the did:web for the LearnCloud itself","tags":["Utilities"],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"string"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}}}
```


# Models

## The error.BAD\_REQUEST object

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"components":{"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information"}}}}
```

## The error.UNAUTHORIZED object

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"components":{"schemas":{"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information"}}}}
```

## The error.FORBIDDEN object

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"components":{"schemas":{"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information"}}}}
```

## The error.INTERNAL\_SERVER\_ERROR object

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"components":{"schemas":{"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information"}}}}
```

## The error.NOT\_FOUND object

```json
{"openapi":"3.1.0","info":{"title":"LearnCloud Storage API","version":"1.0.0"},"components":{"schemas":{"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message"},"code":{"type":"string","description":"The error code"},"issues":{"description":"An array of issues that were responsible for the error","type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information"}}}}
```


# xAPI Reference

## Understanding Key Concepts

{% hint style="success" %}

#### What is xAPI?

xAPI (Experience API) is a specification that allows you to track learning experiences. It uses a simple structure of "Actor - Verb - Object" to describe activities, similar to how you might say "John completed the course" in plain English.

[Learn more about xAPI in LearnCard](/core-concepts/credentials-and-data/xapi-data)
{% endhint %}

{% hint style="info" %}

#### What is a DID?

A DID (Decentralized Identifier) is a unique identifier for your user that works across different systems. Think of it like an email address that works everywhere but is more secure and private.

[Learn more about DIDs in LearnCard](/core-concepts/identities-and-keys/decentralized-identifiers-dids)
{% endhint %}

## Sending xAPI Statements

Here's how to send an xAPI statement to LearnCloud:

```typescript
interface XAPIStatement {
    actor: {
        objectType: 'Agent';
        name: string;
        account: {
            homePage: string;
            name: string;
        };
    };
    verb: {
        id: string;
        display: {
            'en-US': string;
        };
    };
    object: {
        id: string;
        definition: {
            name: { 'en-US': string };
            description: { 'en-US': string };
            type: string;
        };
    };
}

async function sendXAPIStatement(
    statement: XAPIStatement,
    jwt: string,
    endpoint: string = 'https://cloud.learncard.com/xapi'
) {
    const response = await fetch(`${endpoint}/statements`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-Experience-API-Version': '1.0.3',
            'X-VP': jwt,
        },
        body: JSON.stringify(statement),
    });

    return response;
}
```

## Example: Tracking Game Activities

Here are examples of tracking different activities in a skills-building game:

### 1. Tracking Activity Attempts

```typescript
// When a player starts a new challenge
const attemptStatement = {
    actor: {
        objectType: 'Agent',
        name: userDid, // Use the user's DID here
        account: {
            homePage: 'https://www.w3.org/TR/did-core/',
            name: userDid,
        },
    },
    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',
        },
    },
};
```

### 2. Tracking Skill Development

```typescript
// When a player demonstrates a skill
const skillStatement = {
    actor: {
        objectType: 'Agent',
        name: userDid,
        account: {
            homePage: 'https://www.w3.org/TR/did-core/',
            name: userDid,
        },
    },
    verb: {
        id: 'http://adlnet.gov/expapi/verbs/demonstrated',
        display: {
            'en-US': 'demonstrated',
        },
    },
    object: {
        id: 'http://yourgame.com/skills/problem-solving',
        definition: {
            name: { 'en-US': 'Problem Solving' },
            description: { 'en-US': 'Successfully solved a complex game challenge' },
            type: 'http://adlnet.gov/expapi/activities/skill',
        },
    },
};
```

### 3. Tracking Achievements with Results

```typescript
// When a player completes a milestone with specific metrics
const achievementStatement = {
    actor: {
        objectType: 'Agent',
        name: userDid,
        account: {
            homePage: 'https://www.w3.org/TR/did-core/',
            name: userDid,
        },
    },
    verb: {
        id: 'http://adlnet.gov/expapi/verbs/mastered',
        display: {
            'en-US': 'mastered',
        },
    },
    object: {
        id: 'http://yourgame.com/achievements/speed-runner',
        definition: {
            name: { 'en-US': 'Speed Runner' },
            description: { 'en-US': 'Completed level with exceptional speed' },
            type: 'http://adlnet.gov/expapi/activities/performance',
        },
    },
    result: {
        success: true,
        completion: true,
        extensions: {
            'http://yourgame.com/xapi/extensions/completion-time': '120_seconds',
            'http://yourgame.com/xapi/extensions/score': '95',
        },
    },
};
```

### Common Gotchas and Tips

1. **DID Usage**: Always use the same DID in both `actor.name` and `actor.account.name`. This DID should come from your authentication process.
2. **Verb Selection**: Use standard xAPI verbs when possible. Common ones include:
   * attempted
   * completed
   * mastered
   * demonstrated
   * failed
   * progressed
3. **Activity IDs**: Use consistent, unique URLs for your activity IDs. They don't need to be real URLs, but they should be unique identifiers following URL format.
4. **Authentication**: The JWT token should be sent in the `X-VP` header. This is specific to LearnCloud's implementation.
5. **Error Handling**: Always implement proper error handling:

```typescript
try {
    const response = await sendXAPIStatement(statement, jwt);
    if (!response.ok) {
        const error = await response.json();
        console.error('xAPI Statement Error:', error);
    }
} catch (err) {
    console.error('Network Error:', err);
}
```

### Testing Your Implementation

Before sending real user data, test your implementation with sample statements. Verify that:

1. The authentication works (200 status code)
2. The statements are properly formatted
3. The DIDs are correctly included in both required locations
4. The verbs and activity types make sense for your use case

## Reading xAPI Statements

After sending xAPI statements, you can retrieve them using the same endpoint:

```typescript
// Basic GET request for statements
const actor = {
    account: {
        homePage: 'https://www.w3.org/TR/did-core/',
        name: userDid, // Your user's DID
    },
    name: userDid,
};

// Convert actor to URL parameter
const params = new URLSearchParams({
    agent: JSON.stringify(actor),
});

// Fetch statements
const response = await fetch(`${endpoint}/statements?${params}`, {
    method: 'GET',
    headers: {
        'Content-Type': 'application/json',
        'X-Experience-API-Version': '1.0.3',
        'X-VP': jwt, // Your authentication JWT
    },
});
```

### Important Security Notes

1. Users can only read statements about themselves
2. The DID in the JWT (X-VP header) must match the actor's DID
3. A 401 error means either:
   * Invalid authentication
   * Trying to read another user's statements
   * Expired or malformed JWT

### Delegated Access

If you need to allow another party to read statements:

1. Create a delegate credential:

```typescript
const delegateCredential = await userA.invoke.issueCredential({
    type: 'delegate',
    subject: userB.id.did(),
    access: ['read'], // Can be ['read'], ['write'], or ['read', 'write']
});
```

2. Use the delegate credential to create a presentation:

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

3. Use this JWT in the X-VP header to read statements

### Contract-Scoped xAPI Statements

When using delegate credentials through ConsentFlow, you can track which contract was used to make xAPI statements. This allows you to query all statements made through a specific contract.

#### How It Works

When a user consents to a contract, the LearnCard app generates a delegate credential and wraps it in a Verifiable Presentation (VP). To enable contract tracking, the `contractUri` is embedded in the VP before signing:

```typescript
// Create delegate credential for the contract owner
const unsignedDelegateCredential = wallet.invoke.newCredential({
    type: 'delegate',
    subject: contractOwnerDid,
    access: ['read', 'write'],
});

const delegateCredential = await wallet.invoke.issueCredential(unsignedDelegateCredential);

// Create VP and embed contractUri before signing
const unsignedVp: any = await wallet.invoke.newPresentation(delegateCredential);
unsignedVp.contractUri = contractUri; // Add contract URI to VP

const vpJwt = await wallet.invoke.issuePresentation(unsignedVp, {
    proofPurpose: 'authentication',
    proofFormat: 'jwt',
});
```

#### Contract URI Injection

When a statement is sent with a VP containing a `contractUri`, the LearnCloud xAPI service automatically injects it into the statement's `context.extensions`:

```javascript
// The statement you send:
{
    actor: { ... },
    verb: { id: "http://adlnet.gov/expapi/verbs/completed", ... },
    object: { id: "http://yourgame.com/activities/level-1", ... }
}

// Gets stored with the contract URI extension:
{
    actor: { ... },
    verb: { ... },
    object: { ... },
    context: {
        extensions: {
            "https://learncard.com/xapi/extensions/contractUri": "urn:lc:contract:123..."
        }
    }
}
```

#### Querying Statements by Contract

To retrieve all statements made through a specific contract, query statements for the user and filter by the contract extension:

```typescript
const XAPI_CONTRACT_URI_EXTENSION = 'https://learncard.com/xapi/extensions/contractUri';

async function getStatementsByContract(
    actor: object,
    jwt: string,
    contractUri: string,
    endpoint: string = 'https://cloud.learncard.com/xapi'
): Promise<any[]> {
    const params = new URLSearchParams({ agent: JSON.stringify(actor) });

    const response = await fetch(`${endpoint}/statements?${params}`, {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json',
            'X-Experience-API-Version': '1.0.3',
            'X-VP': jwt,
        },
    });

    if (!response.ok) return [];

    const data = await response.json();

    // Filter statements by contract URI
    return (data.statements || []).filter(
        (stmt: any) => stmt.context?.extensions?.[XAPI_CONTRACT_URI_EXTENSION] === contractUri
    );
}
```

#### Use Cases

Contract-scoped xAPI statements enable:

1. **Game/App Analytics**: Track all learning activities that occurred through a specific game or application's contract
2. **Program Reporting**: Generate reports showing all xAPI statements for learners enrolled via a specific program contract
3. **Audit Trails**: Maintain clear records of which third-party contract was responsible for each statement
4. **Multi-Contract Support**: When a user has consented to multiple contracts, distinguish statements by source

### Voiding Statements

To remove a statement (mark it as void):

```typescript
// First get the statement ID from the original POST response
const statementId = (await postResponse.json())[0];

// Create void statement
const voidStatement = {
    actor,
    verb: XAPI.Verbs.VOIDED,
    object: {
        objectType: 'StatementRef',
        id: statementId,
    },
};

// Send void request
const voidResponse = await fetch(`${endpoint}/statements`, {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-Experience-API-Version': '1.0.3',
        'X-VP': jwt,
    },
    body: JSON.stringify(voidStatement),
});
```

Important: You can only void statements that you created.

### Validation Tips

1. Check Response Status:
   * 200: Success
   * 401: Authentication/permission error
   * Other: Server/request error
2. Common Implementation Issues:
   * JWT not matching actor DID
   * Missing or malformed agent parameter
   * Incorrect content type header
   * Missing xAPI version header
3. Testing Checklist:
   * Can read own statements
   * Cannot read others' statements
   * Delegate access works as expected
   * Can void own statements
   * Cannot void others' statements

Remember: The xAPI server maintains strict permissions - users can only read and modify their own statements unless explicitly delegated access by the statement owner.

## Advanced xAPI Statement Queries

### Filtering Large Statement Sets

When dealing with large statement volumes or statements with extensive data in extensions, you can use the following techniques to retrieve more manageable subsets of data.

### Basic Query Parameters

The xAPI API supports several query parameters to limit and filter your results:

```typescript
// Basic query with filtering
const queryParams = new URLSearchParams({
    agent: JSON.stringify(actor),
    limit: '10', // Limit to 10 results
    since: '2024-03-01T00:00:00Z', // Only statements after this date
    until: '2024-03-31T23:59:59Z', // Only statements before this date
    verb: 'http://adlnet.gov/expapi/verbs/completed', // Only specific verb
});

const response = await fetch(`${endpoint}/statements?${queryParams}`, {
    method: 'GET',
    headers: {
        'Content-Type': 'application/json',
        'X-Experience-API-Version': '1.0.3',
        'X-VP': jwt,
    },
});
```

### Key Filtering Parameters

| Parameter   | Description                               | Example                                           |
| ----------- | ----------------------------------------- | ------------------------------------------------- |
| `limit`     | Maximum number of statements to return    | `limit=20`                                        |
| `since`     | ISO 8601 date to filter statements after  | `since=2024-03-01T00:00:00Z`                      |
| `until`     | ISO 8601 date to filter statements before | `until=2024-03-31T23:59:59Z`                      |
| `verb`      | Filter by verb ID                         | `verb=http://adlnet.gov/expapi/verbs/completed`   |
| `activity`  | Filter by activity ID                     | `activity=http://yourgame.com/activities/level-1` |
| `ascending` | Return in ascending order (oldest first)  | `ascending=true`                                  |

### Using Pagination

For very large datasets, implement pagination:

```typescript
// First page
let more = '';
const getPage = async more => {
    const url = more || `${endpoint}/statements?${queryParams.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();

    // Process the statements
    processStatements(data.statements);

    // Check if there are more pages
    return data.more || null;
};

// Initial request
more = await getPage();

// Get next page if available
if (more) {
    more = await getPage(more);
}
```

### Reducing Statement Size

If dealing with extremely large data in extensions:

1. **Reference Instead of Embed**: Store large data elsewhere and include a reference URL in your statement:

```javascript
extensions: {
  "http://yourdomain.com/xapi/extensions/detailed-data": {
    dataId: "123abc",
    dataUrl: "https://storage.yourdomain.com/data/123abc.json"
  }
}
```

2. **Summarize Data**: Include only essential information in statements:

```javascript
extensions: {
  "http://yourdomain.com/xapi/extensions/user-progress": {
    level: "intermediate",
    completionPercentage: 68,
    keyMetrics: ["accuracy:85%", "speed:72", "participation:high"]
  }
}
```

### Specialized Queries

#### Activity-Specific Statements

To retrieve all statements about a specific activity regardless of verb:

```typescript
const activityParams = new URLSearchParams({
    agent: JSON.stringify(actor),
    activity: 'http://yourdomain.com/activities/skill-assessment',
});

const response = await fetch(`${endpoint}/statements?${activityParams}`, {
    // headers as before
});
```

#### Timeline Analysis

To analyze progress over time, sort statements in chronological order:

```typescript
const timelineParams = new URLSearchParams({
    agent: JSON.stringify(actor),
    ascending: 'true',
    since: '2024-01-01T00:00:00Z',
});

const response = await fetch(`${endpoint}/statements?${timelineParams}`, {
    // headers as before
});
```

#### Skill-Based Filtering

To filter statements related to a specific skill or competency:

```typescript
const skillParams = new URLSearchParams({
    agent: JSON.stringify(actor),
    activity: 'http://yourdomain.com/skills/problem-solving',
});

const response = await fetch(`${endpoint}/statements?${skillParams}`, {
    // headers as before
});
```

#### Completion Status

To find all completed activities:

```typescript
const completedParams = new URLSearchParams({
    agent: JSON.stringify(actor),
    verb: 'http://adlnet.gov/expapi/verbs/completed',
    since: '2024-01-01T00:00:00Z',
});

const response = await fetch(`${endpoint}/statements?${completedParams}`, {
    // headers as before
});
```

#### Aggregation Queries

To retrieve summary data rather than individual statements:

```typescript
// First, retrieve statements with aggregation parameter
const aggregateParams = new URLSearchParams({
    agent: JSON.stringify(actor),
    verb: 'http://adlnet.gov/expapi/verbs/experienced',
    since: '2024-01-01T00:00:00Z',
    format: 'ids', // Retrieve only IDs for faster processing
});

const response = await fetch(`${endpoint}/statements?${aggregateParams}`, {
    // headers as before
});

// Then process locally to generate summaries
const statements = await response.json();
const activityCounts = {};

statements.forEach(statement => {
    const activityId = statement.object.id;
    activityCounts[activityId] = (activityCounts[activityId] || 0) + 1;
});

// Now activityCounts shows frequency of each activity
```

### Best Practices for Large Data Sets

1. **Use IDs Effectively**: Query by specific activity IDs to get only statements related to particular challenges or learning objectives
2. **Time-Based Queries**: Filter by recent time periods when monitoring current progress
3. **Aggregate First**: If analyzing patterns, consider creating aggregated statements that summarize multiple detailed statements
4. **Batch Processing**: For analysis, retrieve data in small batches and process incrementally
5. **Cache Common Queries**: If your application frequently needs the same filtered view, consider caching the results


# Embed SDK

Reference for the @learncard/embed-sdk — zero-dependency credential claim button for any website

The Embed SDK lets you add a credential claim button to any webpage with a single script tag and one function call. No framework required — it renders a sandboxed iframe modal that handles the full email OTP verification and credential acceptance flow.

## Installation

{% tabs %}
{% tab title="npm" %}

```bash
npm install @learncard/embed-sdk
```

{% endtab %}

{% tab title="CDN (IIFE)" %}

```html
<script src="https://cdn.jsdelivr.net/npm/@learncard/embed-sdk@latest/dist/learncard.js"></script>
```

{% endtab %}

{% tab title="ESM" %}

```js
import { init } from '@learncard/embed-sdk';
```

{% endtab %}
{% endtabs %}

## Quick Start

```html
<div id="claim-here"></div>

<script src="https://cdn.jsdelivr.net/npm/@learncard/embed-sdk@latest/dist/learncard.js"></script>
<script>
  LearnCard.init({
    publishableKey: 'pk_your_key_here',
    target: '#claim-here',
    credential: { name: 'My Template Name' },
    partnerName: 'Your Organization',
  });
</script>
```

Clicking the rendered button opens the claim modal. The user enters their email, receives an OTP, and accepts the credential into their LearnCard wallet.

## How It Works

```mermaid
sequenceDiagram
    participant Site as Partner Site
    participant SDK as Embed SDK
    participant Modal as Claim Modal
    participant API as LCN API
    participant LCN as LearnCard Network

    Site->>SDK: init(publishableKey, target, credential)
    SDK->>Site: Renders claim button in target element
    Site->>Modal: User clicks — modal opens via srcdoc iframe
    Note over Modal: Step 1 — Email View
    Modal->>SDK: postMessage email address
    SDK->>API: POST /contact-methods/challenge
    API-->>Site: OTP sent to user email
    Note over Modal: Step 2 — OTP View
    Modal->>SDK: postMessage OTP code
    SDK->>API: POST /contact-methods/verify
    API-->>SDK: session JWT
    Note over Modal: Step 3 — Accept View
    Modal->>SDK: postMessage accepted
    SDK->>API: POST /inbox/claim with JWT
    API->>LCN: Issue via Signing Authority and store
    API-->>SDK: credentialId and handoffUrl
    SDK->>Site: onSuccess callback fires
    Note over Modal: Success — confetti and checkmark
```

### Architecture Details

* The modal runs in a **sandboxed `srcdoc` iframe** with `allow-scripts allow-same-origin`. All credential data and session tokens stay inside the iframe; the parent page cannot read them.
* The parent SDK communicates with the iframe via `postMessage` using a shared `nonce` for origin-safe message routing.
* Session JWT is stored in `localStorage` under `lcEmbed:v1:{publishableKey}`, persisting across page navigations so the user stays authenticated for subsequent claims.
* `credential` is resolved server-side: you can pass a full VC object or simply `{ name: 'Template Name' }` and the backend resolves the template by name + your integration's `publishableKey`.

## `init(options)`

Renders a "Claim Credential" button into `target` and wires up the full claim flow.

```ts
import { init } from '@learncard/embed-sdk';

init(options: InitOptions): void
```

### `InitOptions`

| Property                    | Type                                                        | Required    | Description                                                                                                                                    |
| --------------------------- | ----------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `target`                    | `string \| HTMLElement`                                     | ✅           | CSS selector or DOM element to render the button into                                                                                          |
| `credential`                | `CredentialConfig`                                          | ✅           | Full VC object **or** `{ name: 'Template Name' }` for server-side resolution                                                                   |
| `publishableKey`            | `string`                                                    | Recommended | Your integration's publishable key from the developer dashboard. Omit for stub/test mode.                                                      |
| `partnerName`               | `string`                                                    | —           | Displayed in the modal header next to your logo                                                                                                |
| `branding`                  | `BrandingTokens`                                            | —           | Color and logo customization (see below)                                                                                                       |
| `apiBaseUrl`                | `string`                                                    | —           | Defaults to `https://network.learncard.com/api`                                                                                                |
| `requestBackgroundIssuance` | `boolean`                                                   | —           | Issue credential without user interaction (no modal shown)                                                                                     |
| `onSuccess`                 | `(details: ClaimSuccessDetails) => void`                    | —           | Called when user accepts. SDK always opens the wallet URL AND calls this callback. To suppress wallet auto-open, set `branding.walletUrl: ''`. |
| `onEmailSubmit`             | `(email: string) => Promise<EmailSubmitResult>`             | —           | Override default email challenge logic                                                                                                         |
| `onOtpVerify`               | `(email: string, code: string) => Promise<OtpVerifyResult>` | —           | Override default OTP verification logic                                                                                                        |
| `theme`                     | `{ primaryColor?: string }`                                 | —           | Deprecated — use `branding.primaryColor` instead                                                                                               |

### `BrandingTokens`

| Property         | Type     | Description                                                                                              |
| ---------------- | -------- | -------------------------------------------------------------------------------------------------------- |
| `primaryColor`   | `string` | Hex color for buttons, stepper, and card accent. Defaults to `#2EC4A5` (teal).                           |
| `accentColor`    | `string` | Darker accent for hover states. Defaults to `darken(primaryColor, 20%)`.                                 |
| `partnerLogoUrl` | `string` | URL for your organization's logo in the modal header                                                     |
| `logoUrl`        | `string` | Override the LearnCard brand logo                                                                        |
| `walletUrl`      | `string` | URL to open on success. Defaults to `https://learncard.app`. Set to `''` to suppress auto-open entirely. |

### `CredentialConfig`

```ts
// Full VC object (any valid VerifiableCredential JSON):
credential: {
  "@context": ["https://www.w3.org/ns/credentials/v2", ...],
  "type": ["VerifiableCredential", "OpenBadgeCredential", ...],
  ...
}

// Template name shorthand (resolved server-side):
credential: { name: 'Course Completion' }
```

Using `{ name: '...' }` is recommended for production — the generated embed snippet stays stable even if your credential template changes.

### `ClaimSuccessDetails`

```ts
type ClaimSuccessDetails = {
  credentialId: string;   // Issued credential ID
  consentGiven: boolean;  // Whether user gave consent
  handoffUrl?: string;    // Deep link to the credential in the user's wallet
};
```

## Examples

### Custom Branding

```js
init({
  publishableKey: 'pk_...',
  target: '#claim-target',
  credential: { name: 'Course Completion' },
  partnerName: 'Learning Economy Academy',
  branding: {
    primaryColor: '#e11d48',
    accentColor: '#be123c',
    partnerLogoUrl: 'https://your-org.com/logo.png',
    walletUrl: 'https://app.learncard.com',
  },
});
```

### Handle Success in Your UI

```js
init({
  publishableKey: 'pk_...',
  target: '#claim-target',
  credential: { name: 'Course Completion' },
  onSuccess: ({ credentialId, handoffUrl }) => {
    document.getElementById('success-banner').style.display = 'block';
    // handoffUrl is available if you want to link to the wallet
  },
});
```

### Custom Auth Handlers

If you manage your own user sessions, you can bypass the default email/OTP flow:

```js
init({
  target: '#claim-target',
  credential: { name: 'Course Completion' },
  onEmailSubmit: async (email) => {
    await myApi.sendOtp(email);
    return { ok: true };
  },
  onOtpVerify: async (email, code) => {
    const result = await myApi.verifyOtp(email, code);
    if (!result.valid) return { ok: false, error: 'Invalid code' };
    return { ok: true };
  },
});
```

### Stub Mode (No Backend)

Omit `publishableKey` to run the full UI flow without any network calls — useful for local development and visual testing:

```js
init({
  target: '#claim-target',
  credential: { name: 'Test Credential' },
  partnerName: 'My Org',
  // No publishableKey → stub mode, all steps succeed silently
});
```

## Browser Support

The SDK uses `srcdoc` iframe delivery and `postMessage`. Supported in all modern browsers (Chrome, Firefox, Safari, Edge). IE not supported.

## Bundle Size

| Format                   | Raw    | Gzipped      |
| ------------------------ | ------ | ------------ |
| IIFE (`learncard.js`)    | \~37KB | **\~10.5KB** |
| ESM (`learncard.esm.js`) | \~37KB | **\~10.5KB** |

Zero runtime dependencies. The claim modal UI is bundled inline as a minified string.

## See Also

* [How-To: Add an Embed Claim Button to Your Website](/how-to-guides/connect-systems/embed-a-claim-button)
* [Developer Dashboard Guide](/how-to-guides/connect-systems/connect-a-website)
* [Partner Connect SDK](/sdks/partner-connect) — for apps embedded *inside* LearnCard


# SSS Key Manager

Self-contained SSS key management library

`@learncard/sss-key-manager` is a self-contained library that implements Shamir Secret Sharing (SSS) key management for LearnCard. It has zero UI dependencies and can be used in any JavaScript/TypeScript environment.

## Installation

```bash
bun add @learncard/sss-key-manager
```

## Overview

This package provides the `KeyDerivationStrategy` implementation used by the [AuthCoordinator](/core-concepts/architecture-and-principles/auth-coordinator). It handles:

* **Key splitting and reconstruction** using a 2-of-3 Shamir threshold scheme
* **Local device share storage** in IndexedDB with versioning
* **Server communication** for storing and retrieving auth shares
* **Recovery methods** — passkey (WebAuthn PRF), recovery phrase, backup file, email backup
* **Cross-device login** via QR code with ephemeral ECDH encryption
* **Migration** from Web3Auth to SSS

## Architecture

```
sss-key-manager/
├── sss.ts                  # Shamir split (2-of-3) and reconstruct primitives
├── sss-strategy.ts         # KeyDerivationStrategy implementation
├── storage.ts              # IndexedDB device share persistence
├── crypto.ts               # AES-GCM encryption, PBKDF2 key derivation
├── passkey.ts              # WebAuthn PRF-based share encryption
├── recovery-phrase.ts      # BIP39-style mnemonic ↔ share encoding
├── qr-crypto.ts            # ECDH ephemeral key exchange
├── qr-login.ts             # Cross-device login flow
├── atomic-operations.ts    # Split-and-verify with rollback
├── api-client.ts           # Typed fetch wrapper for lca-api routes
└── types.ts                # All types, re-exports from @learncard/types
```

## Key Types

### ContactMethod

Identifies a user by their primary contact method:

```typescript
type ContactMethodType = 'email' | 'phone';

interface ContactMethod {
    type: ContactMethodType;
    value: string;
}
```

### SecurityLevel

Describes how well-protected a user's key is:

```typescript
type SecurityLevel = 'basic' | 'enhanced' | 'advanced';
```

* **basic** — device + server share only (no recovery method)
* **enhanced** — at least one recovery method configured
* **advanced** — multiple recovery methods configured

### RecoveryInput

What the user provides to recover their key:

```typescript
type RecoveryInput =
    | { method: 'passkey'; credentialId: string }
    | { method: 'phrase'; phrase: string }
    | { method: 'backup'; fileContents: string; password: string }
    | { method: 'email'; emailShare: string };
```

### RecoverySetupInput

What the user provides to set up a new recovery method:

```typescript
type RecoverySetupInput =
    | { method: 'passkey' }
    | { method: 'phrase' }
    | { method: 'backup'; password: string; did: string }
    | { method: 'email' };
```

### BackupFile

The JSON structure of a downloadable backup file:

```typescript
interface BackupFile {
    version: 1;
    createdAt: string;
    primaryDid: string;
    shareVersion?: number;
    encryptedShare: {
        ciphertext: string;
        iv: string;
        salt: string;
        kdfParams: {
            algorithm: 'argon2id';
            timeCost: number;
            memoryCost: number;
            parallelism: number;
        };
    };
}
```

## SSSStrategy

The main class that implements `KeyDerivationStrategy`. It is typically instantiated by the AuthCoordinator, not directly by application code.

### Configuration

```typescript
interface SSSStrategyConfig {
    serverUrl: string;
    enableEmailBackupShare?: boolean;
}
```

| Option                   | Default | Description                                                            |
| ------------------------ | ------- | ---------------------------------------------------------------------- |
| `serverUrl`              | —       | Base URL of the lca-api server (e.g., `https://api.example.com/api`)   |
| `enableEmailBackupShare` | `false` | Automatically send a backup share to the user's email during key setup |

### Key Methods

| Method                                             | Purpose                                                               |
| -------------------------------------------------- | --------------------------------------------------------------------- |
| `fetchServerKeyStatus(token, providerType)`        | Check if a key record exists on the server for the authenticated user |
| `setupNewKey(token, providerType, signVp)`         | Generate a new key, split it, store shares on device and server       |
| `reconstructKey(token, providerType)`              | Reconstruct the key from device share + auth share                    |
| `recoverKey(token, providerType, input)`           | Recover the key using a recovery method + auth share                  |
| `hasLocalKey()`                                    | Check if a device share exists in IndexedDB                           |
| `clearLocalKey()`                                  | Remove the device share from IndexedDB                                |
| `setupRecoveryMethod(params)`                      | Set up a new recovery method (passkey, phrase, backup, email)         |
| `getAvailableRecoveryMethods(token, providerType)` | List the user's configured recovery methods                           |

## API Client

The `api-client.ts` module provides a typed fetch wrapper for all lca-api `/keys/*` and `/qr-login/*` routes. It is used internally by the SSS strategy but can also be used directly:

```typescript
import { createApiClient } from '@learncard/sss-key-manager';

const client = createApiClient({ serverUrl: 'https://api.example.com/api' });

const status = await client.getAuthShare({
    authToken: token,
    providerType: 'firebase',
    contactMethod: { type: 'email', value: 'user@example.com' },
});
```

## Testing

The package includes \~249 unit tests across 13 test files:

```bash
bunx nx test sss-key-manager
```


# Auth Types

Provider-agnostic interfaces for authentication and key derivation

The auth types are defined in `@learncard/types` (in `src/auth.ts`) and provide the abstract interfaces used by the [AuthCoordinator](/core-concepts/architecture-and-principles/auth-coordinator), auth providers, and key derivation strategies.

## Installation

```bash
bun add @learncard/types
```

## Interfaces

### AuthProvider

The interface that authentication providers must implement. Handles sign-in, sign-out, and token management.

```typescript
interface AuthProvider {
    /** Get the provider type identifier (e.g., 'firebase') */
    getProviderType(): AuthProviderType;

    /** Get the currently authenticated user, or null */
    getCurrentUser(): Promise<AuthUser | null>;

    /** Get a fresh ID token for the current user */
    getIdToken(): Promise<string>;

    /** Sign out the current user */
    signOut(): Promise<void>;

    /** Subscribe to auth state changes. Returns an unsubscribe function. */
    onAuthStateChanged(callback: (user: AuthUser | null) => void): () => void;

    /** Re-authenticate with a custom token (e.g., after contact method upgrade) */
    reauthenticateWithToken?(token: string): Promise<void>;
}
```

### AuthUser

Represents an authenticated user in a provider-agnostic way:

```typescript
interface AuthUser {
    id: string;
    email?: string;
    phone?: string;
    displayName?: string;
    photoUrl?: string;
    providerType: AuthProviderType;
}
```

### KeyDerivationStrategy

The interface that key derivation strategies must implement. Handles key generation, storage, reconstruction, and recovery.

```typescript
interface KeyDerivationStrategy<
    TRecoveryInput = unknown,
    TSetupInput = unknown,
    TSetupResult = unknown
> {
    /** Check the server for an existing key record */
    fetchServerKeyStatus(token: string, providerType: string): Promise<ServerKeyStatus>;

    /** Generate a new key, split it, and store shares */
    setupNewKey(
        token: string,
        providerType: string,
        signDidAuthVp: (privateKey: string) => Promise<string>
    ): Promise<string>;

    /** Reconstruct the key from device share + auth share */
    reconstructKey(token: string, providerType: string): Promise<string>;

    /** Recover the key using a recovery method */
    recoverKey(token: string, providerType: string, input: TRecoveryInput): Promise<string>;

    /** Check if a device share exists locally */
    hasLocalKey(): Promise<boolean>;

    /** Clear the local device share */
    clearLocalKey(): Promise<void>;

    /** Migrate a legacy key to this strategy */
    migrateFromLegacy?(
        token: string,
        providerType: string,
        legacyKey: string,
        signDidAuthVp: (privateKey: string) => Promise<string>
    ): Promise<string>;

    /** Set up a new recovery method */
    setupRecoveryMethod?(params: {
        token: string;
        providerType: string;
        privateKey: string;
        input: TSetupInput;
        authUser?: AuthUser;
        signDidAuthVp: (privateKey: string) => Promise<string>;
    }): Promise<TSetupResult>;

    /** List available recovery methods for the user */
    getAvailableRecoveryMethods?(
        token: string,
        providerType: string
    ): Promise<RecoveryMethodInfo[]>;
}
```

### ServerKeyStatus

The response from checking the server for an existing key record:

```typescript
interface ServerKeyStatus {
    exists: boolean;
    keyProvider?: 'sss' | 'web3auth';
    securityLevel?: 'basic' | 'enhanced' | 'advanced';
    recoveryMethods?: RecoveryMethodInfo[];
    maskedRecoveryEmail?: string;
    shareVersion?: number;
}
```

### RecoveryMethodInfo

Information about a configured recovery method:

```typescript
interface RecoveryMethodInfo {
    type: string;
    createdAt: Date;
    credentialId?: string;
}
```

### SignInAdapter

The interface for sign-in UI adapters (Phase 2 of auth abstraction). Handles the UI-layer sign-in methods:

```typescript
interface SignInAdapter {
    readonly providerType: AuthProviderType;
    subscribe(onUser: (user: AuthUser | null) => void): () => void;
    sendEmailLink?(email: string, settings: ActionCodeSettings): Promise<void>;
    completeEmailLink?(email: string, link: string): Promise<AuthUser>;
    sendPhoneOtp?(phoneNumber: string): Promise<PhoneVerificationHandle>;
    confirmPhoneOtp?(verificationId: string, code: string): Promise<AuthUser>;
    signInWithOAuth?(provider: 'google' | 'apple'): Promise<AuthUser>;
    signOut(): Promise<void>;
    deleteAccount?(): Promise<void>;
}
```

### AuthSessionError

Typed error class for authentication session issues:

```typescript
class AuthSessionError extends Error {
    constructor(
        message: string,
        public readonly code: 'expired' | 'invalid' | 'network' | 'unknown'
    );
}
```

## Usage

These types are re-exported by both `@learncard/sss-key-manager` and `learn-card-base` for convenience.

```typescript
// From sss-key-manager (SSS-specific types + auth-types re-exports)
import type {
    AuthProvider,
    KeyDerivationStrategy,
    ContactMethod,
} from '@learncard/sss-key-manager';

// From learn-card-base (coordinator types + auth-types re-exports)
import type { AuthProvider, UnifiedAuthState } from 'learn-card-base';

// Direct import (only needed for new provider/strategy implementations)
import type { AuthProvider, KeyDerivationStrategy } from '@learncard/types';
```


# Partner Connect SDK

> Promise-based JavaScript SDK for secure cross-origin communication between partner apps and LearnCard

{% hint style="info" %}
**The runtime types in `packages/learn-card-partner-connect-sdk/src/types.ts` and the Zod validators in `@learncard/types` (`packages/learn-card-types/src/lcn.ts`) are the source of truth.**
{% endhint %}

The Partner Connect SDK transforms complex `postMessage` communication into clean, modern Promise-based functions. It handles the entire cross-origin message lifecycle, including request queuing, origin validation, and timeout management.

## Features

* **🔒 Secure**: Multi-layered origin validation prevents unauthorized access
* **🎯 Type-safe**: Full TypeScript support with comprehensive type definitions
* **⚡ Promise-based**: Modern async/await API eliminates callback complexity
* **🧹 Clean**: Abstracts away all postMessage implementation details
* **📦 Lightweight**: Zero runtime dependencies, \~8KB minified
* **🛡️ Robust**: Built-in timeout handling and structured error management
* **🧪 Standalone-ready**: Runs and demos on its own via automatic mock mode — no host required

## Installation

{% tabs %}
{% tab title="npm" %}

```bash
npm install @learncard/partner-connect
```

{% endtab %}

{% tab title="Bun" %}

```bash
bun add @learncard/partner-connect
```

{% endtab %}

{% tab title="yarn" %}

```bash
yarn add @learncard/partner-connect
```

{% endtab %}
{% endtabs %}

## Quick Start

```typescript
import { createPartnerConnect } from '@learncard/partner-connect';

// Initialize the SDK
const learnCard = createPartnerConnect({
    hostOrigin: 'https://learncard.app',
});

// Request user identity (SSO)
try {
    const identity = await learnCard.requestIdentity();
    console.log('User DID:', identity.user.did);
    console.log('JWT Token:', identity.token);
} catch (error) {
    if (error.code === 'LC_UNAUTHENTICATED') {
        console.log('User is not logged in');
    }
}
```

## API Reference

### Factory Function

#### `createPartnerConnect(options?)`

Creates a new Partner Connect SDK instance.

**Parameters:**

* `options` (`PartnerConnectOptions`, optional): Configuration options. Defaults to `{ hostOrigin: 'https://learncard.app' }`.

**Returns:** `PartnerConnect` instance

**Example:**

```typescript
// Default configuration (uses https://learncard.app)
const learnCard = createPartnerConnect();

// Custom configuration
const learnCard = createPartnerConnect({
    hostOrigin: 'https://learncard.app',
    requestTimeout: 30000,
});
```

{% hint style="info" %}
You can also import and instantiate the `PartnerConnect` class directly:

```typescript
import { PartnerConnect } from '@learncard/partner-connect';
const learnCard = new PartnerConnect();
```

{% endhint %}

### Configuration

#### `PartnerConnectOptions`

```typescript
interface PartnerConnectOptions {
    /**
     * The origin(s) of the LearnCard host
     * Single string or array for query parameter whitelist
     * @default 'https://learncard.app'
     */
    hostOrigin?: string | string[];

    /**
     * Whether to allow native app origins (Capacitor/Ionic)
     * @default true
     */
    allowNativeAppOrigins?: boolean;

    /**
     * Protocol identifier
     * @default 'LEARNCARD_V1'
     */
    protocol?: string;

    /**
     * Request timeout in milliseconds
     * @default 30000
     */
    requestTimeout?: number;

    /**
     * Automatic standalone mock mode.
     * 'auto' (default) mocks only when no LearnCard host is present AND the
     * page runs on a local dev host; 'standalone' mocks whenever no host is
     * present, on any origin; true always mocks; false never mocks.
     * @default 'auto'
     */
    mock?: boolean | 'auto' | 'standalone';

    /**
     * Mock behavior overrides (UI, logging, persistence, fake DID, namespace).
     */
    mockOptions?: MockHostOptions;

    /**
     * Wait (ms) for the host presence probe when embedded in a frame whose
     * parent can't be confirmed as LearnCard.
     * @default 1500
     */
    hostProbeTimeout?: number;
}
```

### Core Methods

#### `requestIdentity()`

Request user identity from LearnCard for Single Sign-On authentication.

**Returns:** `Promise<IdentityResponse>`

**Example:**

```typescript
const identity = await learnCard.requestIdentity();
console.log('User DID:', identity.user.did);
console.log('JWT Token:', identity.token);

// Send token to your backend for validation
await fetch('/api/auth', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${identity.token}` },
});
```

**Response Type:**

```typescript
interface IdentityResponse {
    token: string; // JWT token for backend validation
    user: {
        did: string; // User's decentralized identifier
        [key: string]: unknown;
    };
}
```

#### `sendCredential(input)`

Send a credential to the user's LearnCard wallet. Supports two modes:

**Mode 1: Template-Based Issuance (Recommended for App Store Apps)**

Issue a credential using a pre-configured boost template attached to your App Store listing. LearnCard handles signing and delivery.

**Parameters:**

* `input` (`TemplateCredentialInput`): Template alias and optional data

**Returns:** `Promise<TemplateCredentialResponse>`

```typescript
const result = await learnCard.sendCredential({
    templateAlias: 'course-completion',
    templateData: {
        courseName: 'JavaScript 101',
        completionDate: new Date().toISOString(),
    },
});
console.log('Credential URI:', result.credentialUri);
```

**Mode 2: Raw Credential**

Send a pre-signed verifiable credential directly. Your backend must issue and sign the credential first.

**Parameters:**

* `input` (`unknown`): A signed verifiable credential object

**Returns:** `Promise<SendCredentialResponse>`

```typescript
// Your backend issues the credential
const credential = await yourBackend.issueCredential(identity.user.did);

// Send to user's wallet
const response = await learnCard.sendCredential(credential);
console.log('Credential ID:', response.credentialId);
```

{% hint style="info" %}
For App Store embedded apps, template-based issuance is strongly recommended. See [Connect an Embedded App](/how-to-guides/connect-systems/connect-an-embedded-app) for a complete walkthrough.
{% endhint %}

#### `launchFeature(featurePath, initialPrompt?)`

Navigate the **LearnCard host wallet** to one of its built-in features. Use this when you want to send the user from your embedded app into a wallet route (e.g. AI Topics, Wallet Share, Profile).

{% hint style="warning" %}
**Don't confuse this with `sendNotification({ actionPath })`.** `launchFeature` paths resolve **inside the LearnCard wallet**. `actionPath` paths resolve **inside your embedded app's iframe**. See the [comparison table below](#launchfeature-vs-sendnotification-actionpath).
{% endhint %}

**Parameters:**

* `featurePath` (`string`): Wallet route to navigate to (e.g., `/ai/topics`, `/wallet/share`, `/profile`)
* `initialPrompt` (`string`, optional): Initial prompt or data to pass to the feature

**Returns:** `Promise<void>`

**Errors:** `LC_TIMEOUT`, `UNAUTHORIZED` (if your app lacks the `launch_feature` permission)

**Example:**

```typescript
// Launch AI Tutor with initial prompt
await learnCard.launchFeature(
    '/ai/topics?shortCircuitStep=newTopic',
    'Explain how verifiable credentials work'
);

// Navigate to credential sharing
await learnCard.launchFeature('/wallet/share');
```

#### `launchFeature` vs `sendNotification` `actionPath`

These two APIs both take a `/path` string and look interchangeable, but they navigate to **different origins**:

| API                                | Scope                 | Use when                                                                                                                                    |
| ---------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `launchFeature(path)`              | **LearnCard wallet**  | You want to send the user into a wallet feature (`/ai/topics`, `/wallet/share`, `/profile`). Path is interpreted by the LearnCard host.     |
| `sendNotification({ actionPath })` | **Your embedded app** | You want a notification's tap action to deep-link the user to a route **inside your own app's iframe**. Path is appended to your app's URL. |

**Bridge pattern — deep-linking from a notification to a wallet feature:**

If you want a notification to take the user to a wallet route (e.g. `/ai/topics`), you must route them through one of your own pages that calls `launchFeature()`:

```typescript
// 1. Send a notification with an actionPath that points to YOUR app:
await learnCard.sendNotification({
    title: 'New AI Topic ready',
    body: 'Tap to review your latest tutoring session',
    actionPath: '/ai-topics-bridge', // a real route in YOUR app
});

// 2. In your app, src/pages/ai-topics-bridge.{astro|tsx|html}:
//    On mount, immediately ask the wallet to navigate.
await learnCard.launchFeature('/ai/topics');
```

The wallet's iframe host enforces this: any `actionPath` is appended to your app's iframe URL and the host explicitly rejects values that would escape your origin.

#### `askCredentialSearch(verifiablePresentationRequest)`

Request credentials from the user's wallet using query criteria.

**Parameters:**

* `verifiablePresentationRequest` (`VerifiablePresentationRequest`): Query specification

**Returns:** `Promise<CredentialSearchResponse>`

**Example:**

```typescript
const response = await learnCard.askCredentialSearch({
    query: [
        {
            type: 'QueryByTitle',
            credentialQuery: {
                reason: 'Verify your certification',
                title: 'JavaScript Expert',
            },
        },
    ],
    challenge: `${Date.now()}-${Math.random()}`,
    domain: window.location.hostname,
});

if (response.verifiablePresentation) {
    // User shared credentials - unlock content
    const credentials = response.verifiablePresentation.verifiableCredential;
    unlockPremiumFeatures(credentials);
}
```

#### `askCredentialSpecific(credentialId)`

Request a specific credential by ID.

**Parameters:**

* `credentialId` (`string`): The ID of the credential to request

**Returns:** `Promise<CredentialSpecificResponse>`

**Example:**

```typescript
const response = await learnCard.askCredentialSpecific('credential-id-123');
if (response.credential) {
    console.log('Received credential:', response.credential);
}
```

#### `requestConsent(contractUri?, options?)`

Request user consent for data access permissions.

{% hint style="info" %}
**App Store Apps**: If your app is installed from the LearnCard App Store and has a consent contract configured in its integration settings, you can omit the `contractUri` parameter. The SDK will automatically resolve the configured contract from your listing.
{% endhint %}

**Parameters:**

* `contractUri` (`string`, optional): URI of the consent contract. Can be omitted for App Store apps with configured contracts.
* `options` (`RequestConsentOptions`, optional): Additional options for the consent flow

| Option     | Type      | Default | Description                                                                 |
| ---------- | --------- | ------- | --------------------------------------------------------------------------- |
| `redirect` | `boolean` | `false` | If `true`, redirects to the contract's configured URL after consent granted |

**Returns:** `Promise<ConsentResponse>`

**Examples:**

```typescript
// For App Store apps with configured contracts (recommended)
// The contract is automatically resolved from your listing's integration
const response = await learnCard.requestConsent();

if (response.granted) {
    console.log('User granted consent using listing contract');
}

// With explicit contract URI (for external/non-app store integrations)
const response = await learnCard.requestConsent(
    'lc:network:network.learncard.com/trpc:contract:abc123'
);

if (response.granted) {
    console.log('User granted consent');
} else {
    console.log('User denied consent');
}

// With redirect - redirects to contract's redirectUrl with VP in URL params
const response = await learnCard.requestConsent(undefined, { redirect: true });
```

#### `initiateTemplateIssue(templateId, draftRecipients?)`

Initiate a template-based credential issuance flow.

**Parameters:**

* `templateId` (`string`): ID of the template/boost to issue
* `draftRecipients` (`string[]`, optional): Array of recipient DIDs

**Returns:** `Promise<TemplateIssueResponse>`

**Example:**

```typescript
const response = await learnCard.initiateTemplateIssue(
    'lc:network:network.learncard.com/trpc:boost:xyz789',
    ['did:key:abc', 'did:key:def']
);

if (response.issued) {
    console.log('Template issued successfully');
}
```

***

#### `requestLearnerContext(options?)`

Request comprehensive learner context for AI tutoring and personalization systems.

This method retrieves the user's credentials and personal data (with their consent), then formats them into either an LLM-ready prompt or structured data that can be used to personalize AI experiences.

**Use Cases:**

* AI tutors that adapt to learner's existing skills and credentials
* Personalized learning pathway recommendations
* Smart content that adjusts based on learner history
* Intelligent assessment systems

**Parameters:**

* `options` (`RequestLearnerContextOptions`, optional): Configuration for what data to include and how to format it

| Option                | Type                       | Default     | Description                                          |
| --------------------- | -------------------------- | ----------- | ---------------------------------------------------- |
| `includeCredentials`  | `boolean`                  | `true`      | Whether to include user's credentials in the context |
| `includePersonalData` | `boolean`                  | `false`     | Whether to include personal data (name, bio, etc.)   |
| `format`              | `'prompt' \| 'structured'` | `'prompt'`  | Format of the response                               |
| `instructions`        | `string`                   | -           | Optional instructions to guide LLM prompt generation |
| `detailLevel`         | `'compact' \| 'expanded'`  | `'compact'` | Level of detail in generated content                 |

**Returns:** `Promise<LearnerContextResponse>`

```typescript
interface LearnerContextResponse {
    /** LLM-ready formatted prompt text */
    prompt: string;

    /** Raw structured data (only when format is 'structured') */
    raw?: {
        credentials: unknown[];
        personalData?: Record<string, unknown>;
    };

    /** User's DID */
    did: string;

    /** User's display name if available */
    displayName?: string;
}
```

**Example - AI Tutor Integration:**

```typescript
// Get LLM-ready prompt for an AI tutor
const context = await learnCard.requestLearnerContext({
    includeCredentials: true,
    includePersonalData: true,
    format: 'prompt',
    instructions: 'Focus on technical skills and certifications',
    detailLevel: 'expanded',
});

// Use in AI system prompt
const systemPrompt = `You are a helpful tutor assisting ${context.displayName || 'a learner'}.

${context.prompt}

Adapt your explanations and recommendations based on the learner's background above.`;

// Send to your AI service
const response = await fetch('/api/ai-tutor', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
        systemPrompt,
        userQuestion: 'How do I learn advanced TypeScript?',
    }),
});
```

**Example - Structured Data Access:**

```typescript
// Get raw structured data for custom processing
const context = await learnCard.requestLearnerContext({
    includeCredentials: true,
    includePersonalData: false,
    format: 'structured',
});

// Access credentials directly
console.log('User DID:', context.did);
console.log('Credential count:', context.raw?.credentials.length);

// Process credentials for your own UI
context.raw?.credentials.forEach(cred => {
    console.log('Credential:', cred.name || cred.type);
});
```

**Error Handling:**

```typescript
try {
    const context = await learnCard.requestLearnerContext({
        includeCredentials: true,
        format: 'prompt',
    });

    // Use context.prompt in your AI system
} catch (error) {
    switch (error.code) {
        case 'LC_UNAUTHENTICATED':
            // User not logged in
            showLoginPrompt();
            break;
        case 'USER_REJECTED':
            // User declined to share their data
            showPrivacyNotice();
            break;
        case 'UNAUTHORIZED':
            // App doesn't have permission to request learner context
            console.error('Missing required permissions');
            break;
        default:
            console.error('Failed to get learner context:', error.message);
    }
}
```

{% hint style="info" %}
**Prerequisites for Learner Context:**

1. Your app must be installed from the LearnCard App Store
2. The user must have consented to a contract that grants access to their credentials
3. The consent contract must be configured in your app's listing integration

Use `requestConsent()` before calling `requestLearnerContext()` if the user hasn't consented yet.
{% endhint %}

#### `sendAiSessionCredential(input)`

Send an AI Session credential to record a learning interaction. AI Sessions are organized under AI Topics, creating a structured history of AI tutoring sessions that appears in the user's AI Topics page.

**Use Cases:**

* **AI Tutoring Apps** - Record what was learned during a tutoring session
* **Learning Assistants** - Track learning progress and outcomes
* **Skill Assessment** - Document demonstrated competencies
* **Learning Pathways** - Build a history of learning interactions

**Parameters:**

* `input` (`SendAiSessionCredentialInput`): Session details

| Property       | Type                      | Required | Description                            |
| -------------- | ------------------------- | -------- | -------------------------------------- |
| `sessionTitle` | `string`                  | Yes      | Title of this specific AI session      |
| `summaryData`  | `SummaryCredentialData`   | Yes      | Structured data about what was learned |
| `metadata`     | `Record<string, unknown>` | No       | Optional metadata for the session      |

**Summary Data Structure:**

{% hint style="warning" %}
**Schema verified against SDK v0.2.16.** The fields are `title`, `summary`, `learned`, `skills`, `nextSteps`, `reflections` — **not** `keyTakeaways`/`skillsDemonstrated`/`learningOutcomes`. Earlier versions of these docs showed an incorrect schema; if you're upgrading, double-check your call sites.
{% endhint %}

```typescript
interface SummaryCredentialData {
    /** Short, concise title for the learning session or credential */
    title: string;
    /** Comprehensive summary of what happened during the session */
    summary: string;
    /** Bullet points of key knowledge gained */
    learned: string[];
    /** Categorized skills learned during the session */
    skills: SummaryCredentialSkill[];
    /** Recommended follow-up activities or learning modules */
    nextSteps: SummaryCredentialNextStep[];
    /** Reflections on the learning experience */
    reflections: SummaryCredentialReflection[];
}

interface SummaryCredentialSkill {
    /** Name of the skill category */
    title: string;
    /** Detailed description of what this skill category involves */
    description: string;
}

interface SummaryCredentialNextStep {
    /** Title of the suggested next step */
    title: string;
    /** Description explaining why this next step is recommended */
    description: string;
    /**
     * Optional taxonomy keywords (occupations, careers, jobs, skills, fieldOfStudy).
     * Omit entirely if you don't have taxonomy data.
     */
    keywords?: SummaryCredentialKeyword;
}

interface SummaryCredentialKeyword {
    occupations: string[] | null;
    careers: string[] | null;
    jobs: string[] | null;
    skills: string[] | null;
    fieldOfStudy: string | null;
}

interface SummaryCredentialReflection {
    /** Title of the reflection */
    title: string;
    /** Detailed description of what this reflection involves */
    description: string;
}
```

**Returns:** `Promise<SendAiSessionCredentialResponse>`

```typescript
interface SendAiSessionCredentialResponse {
    topicUri: string; // URI of the AI Topic (parent) boost
    sessionCredentialUri: string; // URI of the created AI Session credential
    sessionBoostUri: string; // URI of the session boost (child of topic)
    isNewTopic: boolean; // True if new topic created, false if reused
}
```

**Example - Recording a Learning Session:**

```typescript
// After conducting an AI tutoring session
const session = await learnCard.sendAiSessionCredential({
    sessionTitle: 'Introduction to Machine Learning',
    summaryData: {
        title: 'Intro to Machine Learning',
        summary:
            'Walked through ML fundamentals: supervised vs unsupervised learning, ' +
            'training data, and an intuitive overview of neural networks.',
        learned: [
            'Machine learning is a subset of AI focused on pattern recognition',
            'Supervised learning uses labeled training data',
            'Neural networks are inspired by biological neurons',
        ],
        skills: [
            {
                title: 'ML Fundamentals',
                description:
                    'Can articulate what machine learning is and how it differs from rule-based programming.',
            },
            {
                title: 'Supervised vs Unsupervised',
                description: 'Distinguishes the two paradigms and gives examples of each.',
            },
        ],
        nextSteps: [
            {
                title: 'Deep Learning Fundamentals',
                description: 'Learn about neural network architectures',
                // keywords is optional — omit it if you have no taxonomy data
            },
            {
                title: 'Build a Simple Classifier',
                description: 'Hands-on practice with scikit-learn',
            },
        ],
        reflections: [
            {
                title: 'Most surprising concept',
                description:
                    'How simple the basic idea of training data is, yet how powerful it becomes at scale.',
            },
        ],
    },
    metadata: {
        duration: 1800, // 30 minutes in seconds
        difficulty: 'beginner',
        topics: ['machine-learning', 'ai', 'neural-networks'],
    },
});

console.log('Topic URI:', session.topicUri);
console.log('Session Credential:', session.sessionCredentialUri);

// 🎉 isNewTopic is a great UX hook: the first session ever from your app
// returns true and creates the AI Topic. Use it to celebrate first-run.
if (session.isNewTopic) {
    showCelebration('First AI session recorded!');
}
```

**How It Works:**

1. **Topic Creation/Reuse**: The first session from your app creates an AI Topic. Subsequent sessions reuse this topic.
2. **Session Credential**: Each call creates a new AI Session credential under the topic.
3. **Automatic Storage**: Credentials are immediately stored in the user's LearnCloud wallet.
4. **AI Topics Page**: Sessions appear in the user's AI Topics section for review.

**Session Hierarchy:**

```
AI Topic (App-level)
├── AI Session 1 (Introduction to Machine Learning)
├── AI Session 2 (Advanced ML Concepts)
├── AI Session 3 (Neural Network Architecture)
└── ...
```

{% hint style="info" %}
**Prerequisites for AI Sessions:**

1. Your app must be installed from the LearnCard App Store
2. The user must have consented to share their learning data
3. Call `requestConsent()` before sending AI sessions if not already consented

The AI Topic is automatically created on the first session and reused for all subsequent sessions from your app.
{% endhint %}

**Errors:** `LC_UNAUTHENTICATED`, `UNAUTHORIZED`, `USER_REJECTED`, `LC_TIMEOUT`, `BAD_REQUEST` (when `summaryData` fails server-side validation — see the [Source-of-Truth note](#schema-source-of-truth) below).

```typescript
try {
    const session = await learnCard.sendAiSessionCredential({
        sessionTitle: 'Learning Session',
        summaryData: {
            /* ... */
        },
    });
} catch (error) {
    switch (error.code) {
        case 'LC_UNAUTHENTICATED':
            showLoginPrompt('Please log in to LearnCard');
            break;
        case 'UNAUTHORIZED':
            showMessage('App not properly configured for AI sessions');
            break;
        case 'USER_REJECTED':
            showMessage('User declined to store the session');
            break;
        case 'BAD_REQUEST':
            console.error('Schema validation failed:', error.message);
            break;
        default:
            console.error('Failed to send session:', error.message);
    }
}
```

#### Schema Source of Truth

The `summaryData` shape is defined by `SummaryCredentialDataValidator` in `@learncard/types` (`packages/learn-card-types/src/lcn.ts`). The brain service deep-validates every `sendAiSessionCredential` call against this discriminated union, so a malformed `summaryData` will fail fast with a clear zod error rather than producing a broken credential.

#### `sendNotification(input)`

Send a notification to the current user from this app. The notification appears in the user's LearnCard notification inbox.

**Parameters:** `AppNotificationInput`

| Property     | Type                 | Required | Description                                                                                                         |
| ------------ | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
| `title`      | `string`             | No       | Notification title.                                                                                                 |
| `body`       | `string`             | No       | Notification body text.                                                                                             |
| `actionPath` | `string`             | No       | **App-local** path. When the user taps the notification, this path is appended to your app's iframe URL. See below. |
| `category`   | `string`             | No       | Optional category tag for grouping notifications.                                                                   |
| `priority`   | `'normal' \| 'high'` | No       | Visual priority. `'high'` notifications are styled more prominently in the inbox.                                   |

**Returns:** `Promise<AppNotificationResponse>`

**Errors:** `LC_UNAUTHENTICATED`, `UNAUTHORIZED`, `LC_TIMEOUT`

{% hint style="warning" %}
**`actionPath` is APP-LOCAL.** It is appended to your embedded app's iframe URL — **not** routed inside the LearnCard wallet. If you want to deep-link into a wallet route (e.g. `/ai/topics`), use the [bridge pattern shown above](#launchfeature-vs-sendnotification-actionpath).
{% endhint %}

**Example — notify within your own app:**

```typescript
await learnCard.sendNotification({
    title: 'Lesson 3 unlocked',
    body: 'Continue where you left off',
    actionPath: '/lessons/3', // a route in YOUR app
    priority: 'normal',
});
```

**Example — bridge to a wallet feature:**

```typescript
// In your app, expose a route that just calls launchFeature on mount:
// src/pages/ai-bridge.tsx
useEffect(() => {
    learnCard.launchFeature('/ai/topics');
}, []);

// Then send a notification that points to your bridge route:
await learnCard.sendNotification({
    title: 'Your AI Topic is ready',
    actionPath: '/ai-bridge',
});
```

#### Counters: `incrementCounter`, `getCounter`, `getCounters`

Lightweight per-user-app counters for tracking app-defined integer state (e.g. "sessions completed", "streak days"). Counters are scoped to **(user, listing)** — every user has an independent set of counters per app.

**Limits (load-bearing — design around these):**

* **Maximum 50 distinct keys** per `(user, app)` pair
* **Maximum 100 writes per minute** per `(user, app)` pair
* **Integer values only** (use `Math.floor` or pre-aggregate if you need fractional state)
* **Key format:** `^[a-zA-Z0-9_-]+$`, 1–64 characters

If you need to track more than 50 things, consolidate (e.g. one `lessons_completed` counter rather than one counter per lesson).

**Signatures:**

```typescript
incrementCounter(key: string, amount: number): Promise<IncrementCounterResponse>;
getCounter(key: string): Promise<GetCounterResponse>;
getCounters(keys?: string[]): Promise<GetCountersResponse>; // omit `keys` to fetch all
```

**Examples:**

```typescript
// Increment by 1 (or any signed integer — pass a negative to decrement)
const { newValue } = await learnCard.incrementCounter('sessions_completed', 1);

// Read one
const { value, updatedAt } = await learnCard.getCounter('sessions_completed');

// Read several (omit the array to fetch every counter for this user-app)
const { counters } = await learnCard.getCounters(['sessions_completed', 'streak_days']);
```

**Errors:** `LC_UNAUTHENTICATED`, `UNAUTHORIZED`, `BAD_REQUEST` (invalid key format, > 50 keys, or rate-limit exceeded), `LC_TIMEOUT`

#### `destroy()`

Clean up the SDK and remove event listeners.

**Returns:** `void`

**Example:**

```typescript
// Clean up when component unmounts or page unloads
learnCard.destroy();
```

#### `isEmbedded()`

Check whether your app is running inside LearnCard (an iframe) or on its own. Use it to change behavior — for example, showing an "Open in LearnCard" prompt when standalone — without writing your own detection.

**Returns:** `boolean` (`false` during server-side rendering)

```typescript
import { isEmbedded } from '@learncard/partner-connect';

if (isEmbedded()) {
    // Inside LearnCard — SDK talks to the real host.
} else {
    // Standalone — show a preview banner, or rely on mock mode (below).
}
```

Also available as `PartnerConnect.isEmbedded()` (static) and `learnCard.isEmbedded()` (instance).

## Standalone / Mock Mode

The SDK only does real work when it's embedded inside LearnCard — that's what answers its requests. Run your app on its own (local dev, a preview deploy, tests) and there's nothing to answer. Standalone calls that aren't mocked reject immediately with `LC_NOT_EMBEDDED` (instead of hanging until the request timeout), plus a one-time console hint.

Mock mode fixes this automatically in local development. Whenever no LearnCard host is present and your app runs on a local dev host (`localhost`, `127.0.0.1`, `[::1]`, `*.localhost`, `*.local`), the SDK stands in for LearnCard so your app stays fully usable:

* **Every method shows a branded toast** describing what would happen once embedded — e.g. `sendCredential` → *"✅ In LearnCard, the user would receive **\[name]** here"*, `incrementCounter` → *"Counter **coins** → **10**"*, `launchFeature` → *"Would open **/wallet**"*. Strong, visible feedback for every call.
* `requestConsent(...)` grants automatically and shows a "mock consent" toast; counters (`incrementCounter` / `getCounter` / `getCounters`) save to the browser and survive reloads.
* Identical or polled calls coalesce into one toast with a ×N counter, so nothing spams the screen.
* `requestIdentity`, notifications, learner context, and sync status return sensible placeholder data.
* Everything is also logged to the console with a `[LearnCard SDK · MOCK]` prefix.

No flags, no separate build in local dev. Your app is demo-able locally and behaves exactly the same against the real host once embedded.

{% hint style="warning" %}
**`'auto'` never mocks on production or remote preview origins.** A real user opening your app's URL directly must never receive a fabricated identity or auto-granted consent. For remote deploy previews (Netlify, Lovable, Vercel, …) that should demo standalone anywhere but use the real host once embedded, opt in with `mock: 'standalone'`. For CI and tests that should always mock, use `mock: true`.
{% endhint %}

| `mock`             | Standalone, local dev | Standalone, remote origin     | Embedded in LearnCard |
| ------------------ | --------------------- | ----------------------------- | --------------------- |
| `'auto'` (default) | mock                  | fail fast (`LC_NOT_EMBEDDED`) | real host             |
| `'standalone'`     | mock                  | mock                          | real host             |
| `true`             | mock                  | mock                          | mock                  |
| `false`            | fail fast             | fail fast                     | real host             |

If your app is embedded in something that isn't LearnCard (a cross-origin Storybook canvas, a preview shell), calls don't hang: the SDK mocks on local dev hosts and otherwise rejects fast with `LC_NOT_EMBEDDED`. When the parent can't be identified (Firefox, or a same-origin localhost wrapper), a one-time side-effect-free presence probe decides — the SDK only mocks if no host answers within `hostProbeTimeout` (default 1500 ms).

Every mocked call shows a labeled toast and a `[LearnCard SDK · MOCK]` console log, so it's clear the SDK is simulating rather than talking to a real host. For a production build meant to run only inside LearnCard, set `mock: false` — standalone calls then reject immediately with `LC_NOT_EMBEDDED`.

```typescript
// Mocks in local dev when standalone; real host when embedded in LearnCard.
const learnCard = createPartnerConnect();

await learnCard.sendCredential({ templateAlias: 'course-completion' });
```

**Overrides:**

```typescript
createPartnerConnect({ mock: 'standalone' }); // mock when no host, on any origin; real when embedded
createPartnerConnect({ mock: true }); // always mock, even embedded (CI, tests)
createPartnerConnect({ mock: false }); // never mock (standalone → LC_NOT_EMBEDDED)
createPartnerConnect({
    mockOptions: {
        ui: true, // toasts/banners (default true)
        log: true, // console logging (default true)
        persist: true, // save counters to the browser (default true)
        namespace: 'my-app-mock', // storage namespace for mock data
        // Seed data for demos / happy-path UI:
        identity: { did: 'did:web:example.com:me', name: 'Ada' },
        credentials: [{ templateAlias: 'course-completion', name: 'Algebra 101' }],
        counters: { coins: 50 },
    },
});
```

Use `learnCard.isMocked()` to check whether an instance is currently mocking.

The mock keeps a small session store, so **reads reflect writes**: after `sendCredential(...)`, calls like `checkUserHasCredential`, `getTemplateRecipients`, `requestLearnerContext`, and `askCredentialSearch` return that credential — so happy-path UI actually lights up standalone. Use `mockOptions.credentials` / `identity` / `counters` to pre-populate state without performing an action first. Mock credentials are marked `_mock: true` and are never cryptographically valid.

## Security Model

The Partner Connect SDK implements comprehensive security measures:

### Origin Validation

**Strict Enforcement:**

* Incoming messages must exactly match the configured host origin
* No wildcard (`*`) origins are ever used
* Query parameter overrides are validated against whitelist

**Configuration Hierarchy:**

1. **Default**: `https://learncard.app` (security anchor)
2. **Query Parameter Override**: `?lc_host_override=https://staging.learncard.app`
3. **Configured Origin**: From `hostOrigin` option

**Example:**

```typescript
// Production configuration
const learnCard = createPartnerConnect({
    hostOrigin: 'https://learncard.app',
});
// Uses: https://learncard.app
// Override: ?lc_host_override=X (not validated, warning logged)

// Staging configuration with whitelist
const learnCard = createPartnerConnect({
    hostOrigin: ['https://learncard.app', 'https://staging.learncard.app'],
});
// Default: https://learncard.app
// Override: ?lc_host_override=https://staging.learncard.app ✅
// Override: ?lc_host_override=https://evil.com ❌
```

### Message Security

* **Protocol Verification**: Messages must match expected protocol version
* **Request ID Tracking**: Only tracked requests are processed
* **Timeout Protection**: Requests automatically timeout to prevent hanging
* **Cleanup on Destroy**: Pending requests are properly rejected

## Error Handling

All SDK methods reject with a `PartnerConnectError` (a real `Error` subclass) carrying both `code` and `message`. The legacy `LearnCardError` shape is preserved — `error.code` keeps working — so existing call sites continue to function unchanged:

```typescript
import { PartnerConnectError } from '@learncard/partner-connect';

try {
    await learnCard.requestLearnerContext();
} catch (err) {
    if (err instanceof PartnerConnectError) {
        // Type-narrowed: TypeScript knows err.code: ErrorCode
        switch (err.code) {
            case 'LC_UNAUTHENTICATED':
                showLogin();
                break;
            case 'USER_REJECTED':
                showPrivacyNotice();
                break;
            case 'UNAUTHORIZED':
                showPermissionsError();
                break;
            default:
                console.error(err);
        }
    }
}
```

The `LearnCardError` interface remains exported for backwards compatibility:

```typescript
interface LearnCardError {
    code: string;
    message: string;
}
```

### Error Codes

| Code                   | Description                                                |
| ---------------------- | ---------------------------------------------------------- |
| `LC_TIMEOUT`           | Request timed out                                          |
| `LC_NOT_EMBEDDED`      | Not embedded in a LearnCard host (standalone, not mocking) |
| `LC_UNAUTHENTICATED`   | User not logged in                                         |
| `USER_REJECTED`        | User declined the request                                  |
| `CREDENTIAL_NOT_FOUND` | Requested credential doesn't exist                         |
| `UNAUTHORIZED`         | User lacks permission                                      |
| `TEMPLATE_NOT_FOUND`   | Template doesn't exist                                     |
| `SDK_NOT_INITIALIZED`  | SDK not properly initialized                               |
| `SDK_DESTROYED`        | SDK was destroyed before completion                        |

### Error Handling Patterns

```typescript
try {
    const result = await learnCard.someMethod();
} catch (error) {
    switch (error.code) {
        case 'LC_UNAUTHENTICATED':
            // Redirect to login or show auth prompt
            showLoginPrompt();
            break;
        case 'LC_TIMEOUT':
            // Show timeout message, offer retry
            showRetryOption();
            break;
        case 'USER_REJECTED':
            // User declined, handle gracefully
            showAlternativeFlow();
            break;
        default:
            // Generic error handling
            showErrorMessage(error.message);
    }
}
```

## Advanced Configuration

### Multiple Origins (Staging Support)

```typescript
const learnCard = createPartnerConnect({
    hostOrigin: [
        'https://learncard.app', // Production
        'https://staging.learncard.app', // Staging
        'https://dev.learncard.app', // Development
    ],
});

// LearnCard host can specify which origin to use:
// Production iframe: https://partner-app.com/
// Staging iframe: https://partner-app.com/?lc_host_override=https://staging.learncard.app
```

### Native App Support

For Capacitor/Ionic apps:

```typescript
const learnCard = createPartnerConnect({
    hostOrigin: 'https://learncard.app',
    allowNativeAppOrigins: true, // Default: true
});

// Automatically accepts messages from:
// - capacitor://localhost
// - ionic://localhost
// - https://localhost:*
// - http://localhost:*
// - http://127.0.0.1:*
```

### Custom Timeouts

```typescript
const learnCard = createPartnerConnect({
    hostOrigin: 'https://learncard.app',
    requestTimeout: 60000, // 60 seconds for slow networks
});
```

## Browser Support

* **Chrome/Edge**: 90+
* **Firefox**: 88+
* **Safari**: 14+
* **Mobile**: iOS Safari 14+, Android Chrome 90+

**Required APIs:**

* `postMessage`
* `Promise`
* `URLSearchParams`
* `addEventListener`

## Migration Guide

### From Manual postMessage

**Before** (80+ lines of boilerplate):

```typescript
const pendingRequests = new Map();

function sendPostMessage(action, payload = {}) {
    return new Promise((resolve, reject) => {
        const requestId = `${action}-${Date.now()}-${Math.random()}`;
        pendingRequests.set(requestId, { resolve, reject });

        window.parent.postMessage(
            {
                protocol: 'LEARNCARD_V1',
                action,
                requestId,
                payload,
            },
            'https://learncard.app'
        );

        setTimeout(() => {
            if (pendingRequests.has(requestId)) {
                pendingRequests.delete(requestId);
                reject({ code: 'LC_TIMEOUT', message: 'Request timed out' });
            }
        }, 30000);
    });
}

window.addEventListener('message', event => {
    if (event.origin !== 'https://learncard.app') return;
    const { protocol, requestId, type, data, error } = event.data;
    if (protocol !== 'LEARNCARD_V1' || !requestId) return;

    const pending = pendingRequests.get(requestId);
    if (!pending) return;

    pendingRequests.delete(requestId);
    if (type === 'SUCCESS') {
        pending.resolve(data);
    } else {
        pending.reject(error);
    }
});

// Usage
const identity = await sendPostMessage('REQUEST_IDENTITY');
```

**After** (3 lines):

```typescript
import { createPartnerConnect } from '@learncard/partner-connect';

const learnCard = createPartnerConnect({
    hostOrigin: 'https://learncard.app',
});

// Usage - same result, much cleaner
const identity = await learnCard.requestIdentity();
```

**Benefits:**

* **85% code reduction** in typical integrations
* **Type safety** with full TypeScript support
* **Better error handling** with structured error codes
* **Security improvements** with origin validation
* **No manual cleanup** required

## Examples

### SSO Authentication Flow

```typescript
async function authenticateUser() {
    try {
        const identity = await learnCard.requestIdentity();

        // Send JWT to your backend for validation
        const response = await fetch('/api/auth/learncard', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                token: identity.token,
                userDid: identity.user.did,
            }),
        });

        if (response.ok) {
            const session = await response.json();
            setUserSession(session);
            showAuthenticatedContent();
        } else {
            showAuthError('Backend validation failed');
        }
    } catch (error) {
        if (error.code === 'LC_UNAUTHENTICATED') {
            showLoginPrompt('Please log in to LearnCard to continue');
        } else {
            showAuthError(error.message);
        }
    }
}
```

### Credential Gating (Premium Content)

```typescript
async function unlockPremiumContent() {
    try {
        const response = await learnCard.askCredentialSearch({
            query: [
                {
                    type: 'QueryByTitle',
                    credentialQuery: {
                        reason: 'Access premium content requires certification',
                        title: 'Premium Membership',
                    },
                },
            ],
            challenge: generateChallenge(),
            domain: window.location.hostname,
        });

        if (response.verifiablePresentation) {
            const credentials = response.verifiablePresentation.verifiableCredential;
            if (validatePremiumCredentials(credentials)) {
                showPremiumContent();
            } else {
                showUpgradePrompt();
            }
        } else {
            showCredentialRequiredMessage();
        }
    } catch (error) {
        if (error.code === 'USER_REJECTED') {
            showAlternativeContent();
        } else {
            showErrorMessage(error.message);
        }
    }
}
```

### Credential Issuance (Certificate Award)

```typescript
async function awardCertificate(courseName, studentDid) {
    try {
        // Issue credential on your backend
        const credential = await fetch('/api/issue-certificate', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                courseName,
                recipientDid: studentDid,
                completionDate: new Date().toISOString(),
            }),
        }).then(r => r.json());

        // Send to student's wallet
        const result = await learnCard.sendCredential(credential);

        showSuccessMessage(
            `Certificate for ${courseName} sent to wallet! ID: ${result.credentialId}`
        );
    } catch (error) {
        if (error.code === 'LC_UNAUTHENTICATED') {
            showMessage('Student must be logged in to receive certificate');
        } else {
            showErrorMessage(`Failed to send certificate: ${error.message}`);
        }
    }
}
```

## TypeScript Types

All types are exported from the package for full type safety:

```typescript
import type {
    // Configuration
    PartnerConnectOptions,

    // Responses
    IdentityResponse,
    SendCredentialResponse,
    TemplateCredentialInput,
    TemplateCredentialResponse,
    TemplateIssueResponse,
    CredentialSearchResponse,
    CredentialSpecificResponse,
    ConsentResponse,
    RequestConsentOptions,

    // Credential queries
    VerifiablePresentationRequest,
    VPRQuery,

    // Errors
    LearnCardError,
    ErrorCode,

    // App events
    AppEvent,
    AppEventResponse,
    SendCredentialEvent,

    // AI Sessions
    SendAiSessionCredentialInput,
    SendAiSessionCredentialResponse,
    SummaryCredentialData,
    SummaryCredentialSkill,
    SummaryCredentialNextStep,
    SummaryCredentialKeyword,
    SummaryCredentialReflection,

    // Notifications
    AppNotificationInput,
    AppNotificationResponse,
} from '@learncard/partner-connect';
```

### Key Type Definitions

```typescript
interface TemplateCredentialInput {
    templateAlias: string;
    templateData?: Record<string, unknown>;
}

interface TemplateCredentialResponse {
    credentialUri: string;
    boostUri: string;
}

interface SendCredentialResponse {
    credentialId: string;
}

interface TemplateIssueResponse {
    issued: boolean;
}

interface ConsentResponse {
    granted: boolean;
}

interface RequestConsentOptions {
    redirect?: boolean;
}

interface MockHostOptions {
    ui?: boolean; // show toasts/banners (default true)
    log?: boolean; // console logging (default true)
    persist?: boolean; // save counters to the browser (default true)
    did?: string; // fake identity DID
    namespace?: string; // storage namespace for mock data
}

interface RequestLearnerContextOptions {
    includeCredentials?: boolean;
    includePersonalData?: boolean;
    format?: 'prompt' | 'structured';
    instructions?: string;
    detailLevel?: 'compact' | 'expanded';
}

interface LearnerContextResponse {
    prompt: string;
    raw?: {
        credentials: unknown[];
        personalData?: Record<string, unknown>;
    };
    did: string;
    displayName?: string;
}

interface SendAiSessionCredentialInput {
    sessionTitle: string;
    summaryData: SummaryCredentialData;
    metadata?: Record<string, unknown>;
}

interface SummaryCredentialData {
    title: string;
    summary: string;
    learned: string[];
    skills: SummaryCredentialSkill[];
    nextSteps: SummaryCredentialNextStep[];
    reflections: SummaryCredentialReflection[];
}

interface SummaryCredentialSkill {
    title: string;
    description: string;
}

interface SummaryCredentialNextStep {
    title: string;
    description: string;
    keywords?: SummaryCredentialKeyword;
}

interface SummaryCredentialKeyword {
    occupations: string[] | null;
    careers: string[] | null;
    jobs: string[] | null;
    skills: string[] | null;
    fieldOfStudy: string | null;
}

interface SummaryCredentialReflection {
    title: string;
    description: string;
}

interface SendAiSessionCredentialResponse {
    topicUri: string;
    sessionCredentialUri: string;
    sessionBoostUri: string;
    isNewTopic: boolean;
}

type ErrorCode =
    | 'LC_TIMEOUT'
    | 'LC_NOT_EMBEDDED'
    | 'LC_UNAUTHENTICATED'
    | 'CREDENTIAL_NOT_FOUND'
    | 'USER_REJECTED'
    | 'UNAUTHORIZED'
    | 'TEMPLATE_NOT_FOUND'
    | string;

interface LearnCardError {
    code: ErrorCode;
    message: string;
}
```

## Related Documentation

* [Connect an Embedded App](/how-to-guides/connect-systems/connect-an-embedded-app) - Step-by-step guide for App Store credential issuance
* [LearnCard Core SDK](https://github.com/learningeconomy/LearnCard/tree/main/sdks/learncard-core/README.md) - Backend credential operations
* [LearnCard Network](https://github.com/learningeconomy/LearnCard/tree/main/sdks/learncard-network/README.md) - Network integration
* [Creating Connected Websites](https://github.com/learningeconomy/LearnCard/tree/main/how-to-guides/connect-systems/connect-a-website/README.md) - Integration guide
* [App Store Development](https://github.com/learningeconomy/LearnCard/tree/main/apps/learn-card-app/README.md) - LearnCard app ecosystem


# Plugins

LearnCard uses a modular plugin system to extend functionality. Plugins are self-contained modules that add specific capabilities through **Control Planes** (standard interfaces like read, store, index) and **Methods** (custom functions via `invoke`).

## Quick Start

Most users don't need to install plugins individually—`@learncard/init` bundles the essential plugins automatically:

```typescript
import { initLearnCard } from '@learncard/init';

// Full wallet with all standard plugins
const learnCard = await initLearnCard({ seed: 'your-seed', network: true });

// Add additional plugins as needed
const enhancedLearnCard = await learnCard.addPlugin(await getMyPlugin());
```

## Official Plugins

### Core Plugins (included in @learncard/init)

| Plugin                                                                                                                      | Description                                          |
| --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| [Crypto](https://github.com/learningeconomy/LearnCard/tree/main/docs/sdks/official-plugins/crypto.md)                       | Core cryptographic operations                        |
| [DIDKit](https://github.com/learningeconomy/LearnCard/tree/main/docs/sdks/official-plugins/didkit.md)                       | DID operations using DIDKit WASM                     |
| [DIDKit Node](https://github.com/learningeconomy/LearnCard/tree/main/docs/sdks/official-plugins/didkit-node.md)             | High-performance native Node.js DIDKit (server-side) |
| [DID Key](https://github.com/learningeconomy/LearnCard/tree/main/docs/sdks/official-plugins/did-key.md)                     | Key management for `did:key` method                  |
| [VC](https://github.com/learningeconomy/LearnCard/tree/main/docs/sdks/official-plugins/vc/README.md)                        | Verifiable Credential signing & verification         |
| [VC-Templates](https://github.com/learningeconomy/LearnCard/tree/main/docs/sdks/official-plugins/vc-templates.md)           | Pre-built credential templates                       |
| [LearnCloud](https://github.com/learningeconomy/LearnCard/tree/main/docs/sdks/official-plugins/learncloud.md)               | Encrypted cloud storage                              |
| [Expiration](https://github.com/learningeconomy/LearnCard/tree/main/docs/sdks/official-plugins/vc/expiration-sub-plugin.md) | Credential expiration handling                       |

### Network Plugins

| Plugin                                                                                                                      | Description                                          |
| --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| [LearnCard Network](https://github.com/learningeconomy/LearnCard/tree/main/docs/sdks/official-plugins/learncard-network.md) | Profiles, connections, boosts via LearnCloud Network |
| [VC-API](https://github.com/learningeconomy/LearnCard/tree/main/docs/sdks/official-plugins/vc-api.md)                       | VC-API protocol support                              |

### Extension Plugins (install separately)

| Plugin                                                                                                                    | Package                              | Description                          |
| ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | ------------------------------------ |
| [Claimable Boosts](https://github.com/learningeconomy/LearnCard/tree/main/docs/sdks/official-plugins/claimable-boosts.md) | `@learncard/claimable-boosts-plugin` | Generate claimable credential links  |
| [LCA API](https://github.com/learningeconomy/LearnCard/tree/main/docs/sdks/official-plugins/lca-api.md)                   | `@learncard/lca-api-plugin`          | LearnCard-managed service support    |
| [CHAPI](https://github.com/learningeconomy/LearnCard/tree/main/docs/sdks/official-plugins/chapi.md)                       | `@learncard/chapi-plugin`            | Credential Handler API integration   |
| [Ceramic](https://github.com/learningeconomy/LearnCard/tree/main/docs/sdks/official-plugins/ceramic.md)                   | `@learncard/ceramic-plugin`          | Ceramic Network storage              |
| [Ethereum](https://github.com/learningeconomy/LearnCard/tree/main/docs/sdks/official-plugins/ethereum.md)                 | `@learncard/ethereum-plugin`         | Ethereum blockchain integration      |
| [VPQR](https://github.com/learningeconomy/LearnCard/tree/main/docs/sdks/official-plugins/vpqr.md)                         | `@learncard/vpqr-plugin`             | QR code generation for presentations |

## Building Your Own Plugin

Want to extend LearnCard? See [Build a Plugin](/how-to-guides/deploy-infrastructure/the-simplest-plugin) for a step-by-step guide.


# LearnCard CLI

**LearnCard CLI** is an easy to use node REPL that instantiates a Learn Card wallet for you and gives you all the tools you need to easily play around with the Learn Card SDK!

### Usage

```bash
npx @learncard/cli

# Optionally specify a deterministic seed to instantiate the wallet with
npx @learncard/cli 1b498556081a298261313657c32d5d0a9ce8285dc4d659e6787392207e4a7ac2
```

### Holder Continuity Export

The CLI also provides REPL helpers from `@learncard/holder-continuity` for holder-controlled continuity exports. These helpers create a standard ZIP with a readable manifest and encrypted payload files for keys, credentials, presentations, consent records, and status-list snapshots.

```javascript
const password = await getLearnCardBundlePassword();

await exportLearnCardBundle(learnCard, {
    out: './learncard-export.zip',
    password,
});

const freshWallet = await initLearnCard({ seed: '0'.repeat(64), network: true });
await importLearnCardBundle('./learncard-export.zip', {
    password,
    wallet: freshWallet,
    verifyBeforeImport: true,
});
```

```javascript
const restoredWallet = await restoreLearnCardFromBundle('./learncard-export.zip', { password });
```

`getLearnCardBundlePassword()` prompts without echoing the password into the REPL, which avoids saving it in REPL history. You can still pass a password string directly for local scripts.

`restoreLearnCardFromBundle(...)` decrypts the exported seed and returns a wallet with the original DID. It does not upload bundle payloads or recreate index records; use `importLearnCardBundle(...)` when copying credentials into another wallet.

If you omit the first argument, the CLI exports the default `learnCard` wallet it created at startup:

```javascript
const password = await getLearnCardBundlePassword();
await exportLearnCardBundle({ out: './learncard-export.zip', password });
```

Lower-level helpers are also available in the REPL:

```javascript
const password = await getLearnCardBundlePassword();
await createLearnCardBundle(learnCard, { password });
await readLearnCardBundle('./learncard-export.zip', { password });
```

For the data model and portability caveats, see [Holder Continuity](/core-concepts/holder-continuity) and the `@learncard/holder-continuity` package `BUNDLE_SPEC.md`.

### Getting Started

<figure><img src="/files/LUoF0txPEq1TEcOzKOQI" alt=""><figcaption><p>Run npx @learncard/cli to boot up the CLI - you should see this screen in your terminal!</p></figcaption></figure>

From within the CLI, you should be able to start playing around with a basic LearnCard. When the CLI boots up, it creates a default LearnCard called `learnCard` that you can interact with.

### Basic Usage

#### View your wallet's DID

One of the easiest ways to interact with your LearnCard is to get its DID:

```javascript
learnCard.id.did();
// 'did:key:z6MkuWb1dvhvime3BZdiuRGi1Q41o4h4hS5ZUizwBiGw3SU6'

learnCard.id.did('pkh:sol');
// 'did:pkh:sol:G4Ky3gTVPE9a54o2DrJsAJW1yVRDHYqCni61MSJv8Dgi'

learnCard.id.did('tz');
// 'did:tz:tz1Y2Rg8ofGpdGpRqHfix3yy4J6qqK19NE5h'

learnCard.id.did('pkh:tz');
// 'did:pkh:tz:tz1Y2Rg8ofGpdGpRqHfix3yy4J6qqK19NE5h'
```

If your LearnCard is initialized to support more DID methods, such as `did:web`, you could retrieve the corresponding DID through this function.

#### Basic Verifiable Credential Issuance & Verification Flow

Once the CLI has booted up, you can start issuing credentials. Try a basic Verifiable Credential issuance and verification flow, for example:

```javascript
// Create a new, unsigned test Verifiable Credential
const unsignedVerifiableCredential = learnCard.invoke.getTestVc();
/**
{
  '@context': [ 'https://www.w3.org/2018/credentials/v1' ],
  id: 'http://example.org/credentials/3731',
  type: [ 'VerifiableCredential' ],
  issuer: 'did:key:z6MkuWb1dvhvime3BZdiuRGi1Q41o4h4hS5ZUizwBiGw3SU6',
  issuanceDate: '2020-08-19T21:41:50Z',
  credentialSubject: { id: 'did:example:d23dd687a7dc6787646f2eb98d0' }
}
*/

// Then, issue the credential to yourself (i.e. sign the credential to turn it into a verifiable credential)
const signedVerifiableCredential = await learnCard.invoke.issueCredential(
    unsignedVerifiableCredential
);
/** 
{
  '@context': [ 'https://www.w3.org/2018/credentials/v1' ],
  id: 'http://example.org/credentials/3731',
  type: [ 'VerifiableCredential' ],
  credentialSubject: { id: 'did:example:d23dd687a7dc6787646f2eb98d0' },
  issuer: 'did:key:z6MkuWb1dvhvime3BZdiuRGi1Q41o4h4hS5ZUizwBiGw3SU6',
  issuanceDate: '2020-08-19T21:41:50Z',
  proof: {
    type: 'Ed25519Signature2018',
    proofPurpose: 'assertionMethod',
    verificationMethod: 'did:key:z6MkuWb1dvhvime3BZdiuRGi1Q41o4h4hS5ZUizwBiGw3SU6#z6MkuWb1dvhvime3BZdiuRGi1Q41o4h4hS5ZUizwBiGw3SU6',
    created: '2022-09-30T15:33:51.015Z',
    jws: 'eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..NkaBWEFT2JCU1ZjMSGmbL72EPhVsAsLykAHULee2uh8YdqBqJbti_FmplQQvGnPDy80pbrFRA-IYQQUx11ISCw'
  }
}
**/

// Then, verify the credential!
// This verifies that the credential is valid, has not been tampered with, and was issued by the correct DID
await learnCard.invoke.verifyCredential(signedVerifiableCredential);
/**
[
  { status: 'Success', check: 'proof', message: 'Valid' },
  {
    status: 'Success',
    check: 'expiration',
    message: 'Valid • Does Not Expire'
  }
]
**/
```

#### Basic Verifiable Presentation Issuance & Verification Flow

Now, take the `signedVerifiableCredential` you created in the [VC issuance flow above](#basic-verifiable-credential-issuance-and-verification-flow), and try wrapping it into a Verifiable Presentation, and verifying it.

<pre class="language-javascript"><code class="lang-javascript">// Get an unsigned, test Verifiable Presentation template containing your signed VC
const unsignedVerifiablePresentation = await learnCard.invoke.newPresentation(signedVerifiableCredential);
/**
{
  '@context': [ 'https://www.w3.org/2018/credentials/v1' ],
  type: [ 'VerifiablePresentation' ],
  holder: 'did:key:z6MkuWb1dvhvime3BZdiuRGi1Q41o4h4hS5ZUizwBiGw3SU6',
  verifiableCredential: {
    '@context': [ 'https://www.w3.org/2018/credentials/v1' ],
    id: 'http://example.org/credentials/3731',
    type: [ 'VerifiableCredential' ],
    credentialSubject: { id: 'did:example:d23dd687a7dc6787646f2eb98d0' },
    issuer: 'did:key:z6MkuWb1dvhvime3BZdiuRGi1Q41o4h4hS5ZUizwBiGw3SU6',
    issuanceDate: '2020-08-19T21:41:50Z',
    proof: {
      type: 'Ed25519Signature2018',
      proofPurpose: 'assertionMethod',
      verificationMethod: 'did:key:z6MkuWb1dvhvime3BZdiuRGi1Q41o4h4hS5ZUizwBiGw3SU6#z6MkuWb1dvhvime3BZdiuRGi1Q41o4h4hS5ZUizwBiGw3SU6',
      created: '2022-09-30T15:33:51.015Z',
      jws: 'eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..NkaBWEFT2JCU1ZjMSGmbL72EPhVsAsLykAHULee2uh8YdqBqJbti_FmplQQvGnPDy80pbrFRA-IYQQUx11ISCw'
    }
  }
}
**/

// Issue (sign) the Verifiable Presentation 
<strong>const verifiablePresentation = await learnCard.invoke.issuePresentation(unsignedVerifiablePresentation);
</strong>/**
{
  '@context': [ 'https://www.w3.org/2018/credentials/v1' ],
  type: [ 'VerifiablePresentation' ],
  verifiableCredential: {
    '@context': [ 'https://www.w3.org/2018/credentials/v1' ],
    id: 'http://example.org/credentials/3731',
    type: [ 'VerifiableCredential' ],
    credentialSubject: { id: 'did:example:d23dd687a7dc6787646f2eb98d0' },
    issuer: 'did:key:z6MkuWb1dvhvime3BZdiuRGi1Q41o4h4hS5ZUizwBiGw3SU6',
    issuanceDate: '2020-08-19T21:41:50Z',
    proof: {
      type: 'Ed25519Signature2018',
      proofPurpose: 'assertionMethod',
      verificationMethod: 'did:key:z6MkuWb1dvhvime3BZdiuRGi1Q41o4h4hS5ZUizwBiGw3SU6#z6MkuWb1dvhvime3BZdiuRGi1Q41o4h4hS5ZUizwBiGw3SU6',
      created: '2022-09-30T15:33:51.015Z',
      jws: 'eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..NkaBWEFT2JCU1ZjMSGmbL72EPhVsAsLykAHULee2uh8YdqBqJbti_FmplQQvGnPDy80pbrFRA-IYQQUx11ISCw'
    }
  },
  proof: {
    type: 'Ed25519Signature2018',
    proofPurpose: 'assertionMethod',
    verificationMethod: 'did:key:z6MkuWb1dvhvime3BZdiuRGi1Q41o4h4hS5ZUizwBiGw3SU6#z6MkuWb1dvhvime3BZdiuRGi1Q41o4h4hS5ZUizwBiGw3SU6',
    created: '2022-09-30T15:41:39.175Z',
    jws: 'eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..iOAHkQiIp5fi9IiuKIxCAnTQ-A7BL8cbCVAF_-pwDr5uvewWHuk1UISoElVSzeDxBO_OjsjDJ4qZeowwZ4WEDA'
  },
  holder: 'did:key:z6MkuWb1dvhvime3BZdiuRGi1Q41o4h4hS5ZUizwBiGw3SU6'
}
**/

// Verify the Verifiable Presentation has not been tampered with.
await learnCard.invoke.verifyPresentation(verifiablePresentation);
/**
{ checks: [ 'proof' ], warnings: [], errors: [] }
**/
</code></pre>

#### Initialize more LearnCards

At any point, you can initialize additional LearnCards in the CLI, which can be helpful for testing cross-wallet flows:

```javascript
// Initialize an empty LearnCard (cannot sign credentials)
const emptyLC = await initLearnCard();

// Initialize a new LearnCard with deterministically seeded key material
const seededLC = await initLearnCard({ seed: 'abc123' });
```

Check out the docs on [initializing LearnCards](/sdks/learncard-core/construction#the-initlearncard-function) for more ways to create a LearnCard.

#### And beyond!

There is a ton of functionality exposed through the CLI. Explore the Usage Examples in LearnCard Wallet SDK:

{% content-ref url="/pages/4umV73ZRVcfyu5TTw8Na" %}
[Usage Examples](/sdks/learncard-core/construction)
{% endcontent-ref %}


# Identities & Keys

DIDs, Wallets, Key Management, Profile Types

## **What is this section about?**

This section explores how individuals, organizations, and even automated services are uniquely identified within the LearnCard ecosystem. We'll cover the different types of digital identities used, how they are created and managed, and the crucial role of cryptographic keys in keeping everything secure and interactions verifiable.

### **Why is this important for you to understand?**

Secure and verifiable identity is the bedrock of trust in any digital interaction. Whether you're issuing a credential, receiving one, or verifying one, knowing who's who and how their identity is asserted and protected is paramount. Understanding these concepts helps you manage users and services effectively, control access, and ensure the integrity of all operations. It's like knowing the difference between a personal ID, a business license, and the keys that prove you own them.

### **What you'll learn:**

You'll learn about Decentralized Identifiers (DIDs), the role of digital Wallets, the basics of Key Management (like public and private keys), and the different Profile Types that exist within LearnCard and when to use them.

***

### 🧰 Wallets

A **wallet** is where credentials live. It:

* Stores verifiable credentials
* Signs and verifies credentials
* Manages identity (via DIDs)
* Handles selective sharing and consent

LearnCard gives you an in-app wallet that can be embedded in your own app, bot, or backend.

***

### 🪪 Decentralized Identifiers (DIDs)

DIDs are unique, cryptographically-verifiable identifiers that aren’t tied to centralized registries.

* They let users prove ownership without relying on usernames or emails.
* LearnCard generates and manages DIDs under the hood.

DIDs are what make trust portable—and private.

***

### 🔁 DID Authentication

**DID Authentication** lets users prove ownership of their DID to log in or access services.

* Works like OAuth, but self-sovereign
* No passwords or emails needed
* Used across LearnCard to authorize actions


# Decentralized Identifiers (DIDs)

Decentralized Identifiers (DIDs) are a type of globally unique identifier that enables verifiable, decentralized digital identity. Unlike traditional identifiers, DIDs are:

* **Self-sovereign**: Controlled by the identity owner, not a central authority
* **Persistent**: Do not require the continued operation of an underlying organization
* **Cryptographically verifiable**: Allow the controller to prove control without requiring permission

A DID looks like this: `did:method:specific-idstring`, where the method specifies how the DID operates.

```mermaid
graph TD
    subgraph "DID Structure"
        DID["did:method:specificId"]
        DID --> Method["Method (e.g., key, web, ethereum)"]
        DID --> Identifier["Method-Specific Identifier"]
        DID --- Document["DID Document"]
        Document --> VerificationMethods["Verification Methods (Keys)"]
        Document --> Authentication["Authentication Methods"]
        Document --> Services["Service Endpoints"]
    end
```

## DID Methods

LearnCard supports multiple DID methods, each with different characteristics:

<table data-header-hidden data-full-width="true"><thead><tr><th>DID Method</th><th>Description</th><th>Common Use Case</th></tr></thead><tbody><tr><td>did:key</td><td>Generates a digital ID directly from a cryptographic key, often stored locally (e.g., in a JWK file).</td><td>Simple IDs tied directly to a specific crypto key.</td></tr><tr><td>did:web</td><td>Uses a standard website address (domain name) to make a digital ID's information publicly and securely findable online.</td><td>Digital IDs for organizations or websites.</td></tr><tr><td>did:pkh</td><td>Creates a digital ID directly from a public blockchain address (like a crypto wallet address), supporting multiple blockchain types through a common standard (CAIP).</td><td>Linking digital IDs to various blockchain accounts.</td></tr><tr><td>did:jwk</td><td>Directly transforms a cryptographic key (in JWK format) into a full digital ID and its associated information.</td><td>Representing a cryptographic key as a complete digital ID.</td></tr><tr><td>did:test</td><td>Generates sample digital IDs and related data, specifically for testing software that uses DIDs.</td><td>Testing and ensuring DID systems work correctly.</td></tr><tr><td>did:ethr</td><td>A widely-used method for digital IDs on the Ethereum blockchain, common in many Web3 applications.</td><td>Digital IDs for apps and services on Ethereum.</td></tr><tr><td>did:ion</td><td>Creates highly scalable and decentralized digital IDs using the Bitcoin network, secured by the Sidetree protocol.</td><td>Scalable and secure IDs on the Bitcoin blockchain.</td></tr><tr><td>did:tezos</td><td>Creates digital IDs that are linked to accounts on the Tezos blockchain.</td><td>Digital IDs for users and apps on Tezos.</td></tr></tbody></table>

## Verifiable Proof Types

<table data-header-hidden data-full-width="true"><thead><tr><th width="318.6605224609375"></th><th></th><th></th></tr></thead><tbody><tr><td>Proof Type</td><td>Description</td><td>Common Use Case</td></tr><tr><td>RSASignature2018</td><td>A widely recognized digital signature method using RSA cryptography, common in traditional web security.</td><td>Verifying authenticity in systems familiar with established RSA standards.</td></tr><tr><td>Ed25519VerificationKey2018 / Ed25519Signature2018</td><td>A modern, fast, and secure digital signature method using Ed25519 keys.</td><td>High-performance, secure verification in newer systems and many blockchains.</td></tr><tr><td>EcdsaSecp256k1Signature2019</td><td>A digital signature method widely used by Bitcoin, Ethereum, and other blockchain platforms (uses the secp256k1 curve).</td><td>Securing transactions and identities in many popular blockchain ecosystems.</td></tr><tr><td>EcdsaSecp256r1Signature2019</td><td>A digital signature method commonly used in web security (like TLS/SSL) and various industry standards (uses the P-256 curve).</td><td>Ensuring authenticity in standard web communications and enterprise applications.</td></tr><tr><td>EcdsaSecp256k1RecoverySignature2020</td><td>A specialized digital signature (secp256k1 curve) where the signer's public key (or address) can be found directly from the signature itself.</td><td>Creating compact proofs in systems like Ethereum where sender identity is derived.</td></tr><tr><td>Eip712Signature2021</td><td>A way to sign structured, human-readable data on Ethereum, making it clearer to users what they are approving.</td><td>User-friendly signing of detailed information in Ethereum applications.</td></tr><tr><td>JsonWebSignature2020 (JWS)</td><td>A standard method for creating digital signatures using JSON, offering flexibility with different signature algorithms.</td><td>Securely signing data for web applications and APIs, commonly used with JWTs.</td></tr></tbody></table>

## ID Control Plane

When using the [LearnCard Wallet SDK](/sdks/learncard-core), the ID control plane provides standardized methods for working with DIDs:

```mermaid
sequenceDiagram
    participant App as Application
    participant LC as LearnCard Wallet SDK
    participant IdPlane as ID Control Plane
    
    App->>LC: learnCard.id.did("key")
    LC->>IdPlane: Generate DID using key method
    IdPlane-->>LC: Return "did:key:z6Mk..."
    LC-->>App: "did:key:z6Mk..."
    
    App->>LC: learnCard.id.keypair("Ed25519")
    LC->>IdPlane: Generate Ed25519 keypair
    IdPlane-->>LC: Return JWK
    LC-->>App: {kty: "OKP", crv: "Ed25519", ...}

```


# Seed Phrases

Understanding Seed Phrases (for Account & Key Recovery)

**What is this section about?** This section explains "Key Generation Seeds" as they are used within the LearnCard ecosystem. We'll cover what these seeds are, why they are critically important for creating and securing digital identities and cryptographic keys, and how to handle them safely.

**Why is this important for you to understand?** In LearnCard, a seed is the foundational secret from which all of a user's or service's cryptographic keys are derived. If you are managing the raw key material for an identity, understanding how this seed works is essential for security, control, and potential account/key recovery. Mismanagement of this seed can lead to irreversible loss of access or compromised identity.

**What you'll learn:**

* What a LearnCard "seed" specifically refers to (a hexadecimal string).
* How these seeds are used to generate keys.
* Best practices for generating and securely managing these seeds.
* The distinction between this type of seed and mnemonic "seed phrases."

***

#### What is a LearnCard Key Generation Seed?

In the context of initializing LearnCard (e.g., with `initLearnCard({ seed: 'your-hex-string' })`), a **Seed** is a specific piece of data: **a 64-character hexadecimal string.** This string represents 32 bytes of randomness (entropy) and serves as the master input for deterministically generating all cryptographic keys associated with a LearnCard identity or wallet.

**Think of this hexadecimal seed as the root secret or the "DNA" for an identity's cryptographic capabilities.**

#### How Seeds Work in LearnCard

1. **Source of Entropy:** The seed provides the necessary randomness required to create strong, unpredictable cryptographic keys.
2. **Deterministic Key Derivation:** From this single hexadecimal seed, LearnCard (often utilizing underlying libraries like DIDKit) can generate a consistent set of multiple key pairs (public and private keys) for different cryptographic algorithms and purposes. "Deterministic" means that if you provide the exact same seed again, you will always get the exact same keys.
3. **Hexadecimal Input:**
   * The `seed` parameter in `initLearnCard` expects this hexadecimal string.
   * **Important:** If you provide a string that is not 64 characters long, `initLearnCard` will typically prefix it with zeroes until it reaches the required 64-character length. This means that, for example, `'1'` and `'0000...001'` (63 zeroes followed by a 1) would be treated as identical seeds.

#### Generating a Secure Seed

The responsibility for generating and securely storing this seed lies with you, the developer or the system integrating LearnCard. Insufficient randomness (entropy) or insecure storage can severely compromise the security of the identities and data.

{% hint style="warning" %}
There be dragons here. 🐉 In production environments, take great care and caution when generating and storing key material. Insufficient entropy or insecure storage, among other vectors, can easily compromise your data and identities.

Key input should be a hexadecimal string. If you pass a string that is not valid hex, an error will be thrown
{% endhint %}

Here are examples of how to generate a cryptographically secure 32-byte random value and convert it to the required 64-character hexadecimal string:

* **In a Browser Environment:**

  ```typescript
  const randomKeyHex = Array.from(crypto.getRandomValues(new Uint8Array(32)), dec =>
      dec.toString(16).padStart(2, '0')
  ).join('');
  // randomKeyHex will be a 64-character hexadecimal string
  ```
* **In a Node.js Environment:**

  ```typescript
  import crypto from 'node:crypto';

  const randomKeyHex = crypto.randomBytes(32).toString('hex');
  // randomKeyHex will be a 64-character hexadecimal string
  ```

#### The Critical Importance of Securing Your Seed

Because this hexadecimal seed is the master secret for an identity's keys within LearnCard:

* **It IS the Master Key:** If someone gains access to this 64-character hex string, they can regenerate all associated private keys and take full control of the identity and any credentials or assets it controls.
* **Loss Means Irreversibility:** If you (or your user) lose this seed and there are no other backups of the private keys themselves, access to the identity and its capabilities may be permanently lost.
* **Security Practices (similar to mnemonic seed phrases, but for a hex string):**
  * **Store Securely:** This hex string must be stored with extreme care. For end-users managing their own seeds, this often means writing it down accurately and storing it offline in multiple secure locations (e.g., a safe). For backend systems managing seeds, this involves robust secret management solutions (e.g., Hardware Security Modules (HSMs), managed KMS services).
  * **Accuracy is Crucial:** Unlike mnemonic phrases designed for easier human transcription, a 64-character hex string is prone to transcription errors if handled manually.
  * **Never Transmit Insecurely:** Avoid sending it over unencrypted channels or storing it in easily accessible digital locations.
  * **Do Not Hardcode (in client-side code):** For applications where users control their identities, the seed should be managed by the user or a secure wallet mechanism, not hardcoded into the application.

#### Seeds in LearnCard Initialization

When you initialize LearnCard with a seed, for example:

```typescript
// Make sure you have the didkit plugin installed! bun add @learncard/didkit-plugin
import { initLearnCard } from '@learncard/init';
import didkit from '@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm'; // Example for Webpack 5

const learnCard = await initLearnCard({
    seed: 'abc123your64characterhexstringgoeshere...',
    didkit,
});
```

The LearnCard SDK uses this hexadecimal seed to deterministically generate the cryptographic keys needed for DID creation, signing, and other operations. This allows for consistent identity representation and control as long as the seed is known.

#### Distinction from Mnemonic Seed Phrases

It's important to distinguish LearnCard's direct use of a hexadecimal seed string from **mnemonic seed phrases** (e.g., the 12-24 words used by many cryptocurrency wallets, often following the BIP-39 standard).

* **Mnemonic Seed Phrases:** These are designed to be a more human-readable and writable way to back up the entropy (randomness) needed to generate keys. The words themselves are converted into the actual binary seed/entropy.
* **LearnCard's `seed` parameter:** Expects the direct hexadecimal representation of the 32-byte entropy.

While you *could* technically generate a 32-byte entropy, convert it to a BIP-39 mnemonic phrase for user backup, and then convert that mnemonic *back* to its 32-byte hex representation to pass to `initLearnCard`, LearnCard's `seed` parameter itself does not directly consume the list of words. It consumes the resulting hex string.

#### Relationship to SSS Key Management

In the LearnCard App and other end-user applications, private keys are no longer stored or managed as raw seeds by the user. Instead, the [SSS Key Management](/core-concepts/identities-and-keys/key-management-sss) system automatically generates the key, splits it into three shares using Shamir Secret Sharing, and handles storage and recovery transparently.

The seed-based initialization described on this page is primarily relevant for:

* **Backend services** and **bots** that manage their own identity programmatically.
* **Self-hosted deployments** where you control the key material directly.
* **Plugin development** and **testing** scenarios.

For end-user applications, see [Key Management (SSS)](/core-concepts/identities-and-keys/key-management-sss) and [Account Recovery](/core-concepts/identities-and-keys/account-recovery) for how keys are protected and recovered.

#### Key Takeaways

* A LearnCard Key Generation Seed is a **64-character hexadecimal string** representing 32 bytes of randomness.
* It is the **foundational secret** used to deterministically derive all cryptographic keys for a LearnCard identity.
* **Secure generation and extremely careful storage** of this seed are critical for maintaining control and enabling recovery of an identity.
* This direct hexadecimal seed is distinct from, though related to the concept of, mnemonic seed phrases commonly used for wallet backups.
* For end-user applications, **SSS key management** handles key protection automatically — users do not need to manage raw seeds.


# Key Management (SSS)

How LearnCard protects private keys using Shamir Secret Sharing

## What is this section about?

This section explains how LearnCard protects user private keys using **Shamir Secret Sharing (SSS)** — a cryptographic technique that splits a secret into multiple shares so that no single party ever holds the complete key. This replaces the previous Web3Auth-based key management system with a fully self-hosted solution.

## Why is this important?

Private keys are the foundation of a user's digital identity. Whoever controls the key controls the identity, its credentials, and all associated data. SSS ensures that:

* **No single point of compromise** — neither the device, the server, nor any recovery method alone can reconstruct the key.
* **No third-party dependency** — key management is fully self-hosted, unlike the previous Web3Auth system.
* **Flexible recovery** — users can recover their key even if they lose their device, using any of several recovery methods.

## How It Works

### The 2-of-4 Threshold Scheme

When a user's private key is created (or migrated), it is split into **four shares** using Shamir's Secret Sharing with a **threshold of 2**. Any two of the four shares are sufficient to reconstruct the original key. No single share reveals anything about the key on its own.

```
Private Key (32 bytes)
    │
    ├── Split via Shamir 2-of-4 ──────────────────────────┐
    │                                                      │
    ▼                  ▼                 ▼                 ▼
Device Share      Auth Share       Recovery Share      Email Share
(IndexedDB)    (lca-api server,    (passkey, phrase,   (encrypted email
                AES-256-GCM        or backup file)     backup)
                encrypted at rest)
                                                           │
Any 2 shares → reconstruct private key ◄──────────────────┘
```

### The Four Shares

#### 1. Device Share

* Stored locally on the user's device in **IndexedDB**.
* Versioned and keyed to the user's contact method (email or phone).
* Available immediately on the device where the key was created.
* **Lost when the user clears browser storage or switches devices.**

#### 2. Auth Share (Server Share)

* Stored on the LearnCard API server (`lca-api`).
* **Encrypted at rest** using AES-256-GCM with a key derived from a server-side seed via HKDF. The server never stores the share in plaintext.
* Retrieved by authenticating with the user's auth provider (e.g., Firebase).
* Supports **share versioning** — when shares are rotated, previous versions are kept so that recovery methods created against older shares remain valid.

#### 3. Recovery Share

The recovery share can be protected by the user in multiple ways:

| Method                     | How the share is protected                                                                         | Storage            |
| -------------------------- | -------------------------------------------------------------------------------------------------- | ------------------ |
| **Passkey (WebAuthn PRF)** | Encrypted using a key derived from the passkey's PRF output — hardware-bound, phishing-resistant   | Server (encrypted) |
| **Recovery Phrase**        | Encoded as a BIP39-style mnemonic that the user writes down                                        | User (offline)     |
| **Backup File**            | Encrypted with a user-chosen password using Argon2id + AES-GCM, stored as a downloadable JSON file | User (file)        |

#### 4. Email Share

* Encrypted before being sent to the user's verified recovery email.
* Provides an additional independent recovery path.
* Optional; deployments can disable email backup.

### Normal Login (Same Device)

When a user logs in on a device where they previously set up their key:

1. The **device share** is read from IndexedDB.
2. The **auth share** is fetched from the server (requires a valid auth token).
3. The two shares are combined to reconstruct the private key.
4. The key is used for the session and never persisted in plaintext.

### New Device Login

When a user logs in on a new device (no device share available):

1. Authentication succeeds, but the device share is missing.
2. The user enters **recovery mode** and is prompted to use one of their recovery methods.
3. The **recovery share** or **email share** is combined with the **auth share** to reconstruct the key.
4. A **new device share** is generated and stored locally. The key is re-split so that all four shares are fresh.

***

## Security Levels

LearnCard defines three security levels based on how many recovery methods a user has configured:

| Level        | Requirements                                       | Risk Profile                                                                     |
| ------------ | -------------------------------------------------- | -------------------------------------------------------------------------------- |
| **Basic**    | Device share + server share only                   | If the user loses their device and clears storage, the key is **unrecoverable**. |
| **Enhanced** | + at least one recovery method (passkey or phrase) | The user can recover on a new device using the recovery method + server share.   |
| **Advanced** | + multiple recovery methods                        | Maximum resilience — multiple independent paths to recovery.                     |

Users are prompted to set up recovery methods after their initial key setup. A persistent banner appears until at least one recovery method is configured.

***

## Share Versioning

When shares are rotated (e.g., after a recovery event or contact method upgrade), the server retains **previous auth share versions** in a history array. This ensures that recovery methods created against an older share version can still be used — the server can serve the correct historical auth share by `shareVersion` number.

Orphaned recovery methods (those referencing share versions no longer in the history) are automatically pruned.

***

## Server-Side Encryption

The auth share is never stored in plaintext on the server. The encryption flow:

1. A **Data Encryption Key (DEK)** is randomly generated per share.
2. The share is encrypted with the DEK using **AES-256-GCM**.
3. The DEK itself is encrypted using a **Key Encryption Key (KEK)** derived from the server's `SEED` environment variable via **HKDF-SHA256**.
4. Both the encrypted share and the encrypted DEK are stored together.

This means even a database breach does not expose the shares without the server's `SEED`.

***

## Migration from Web3Auth

Existing users who were on the previous Web3Auth-based key management system are automatically detected and migrated:

1. The **AuthCoordinator** detects that the user has an existing account but no SSS record on the server.
2. It enters a `needs_migration` state.
3. The legacy private key is retrieved from the cached Web3Auth Single Factor Auth (SFA) key.
4. The key is split into four shares using SSS, and the shares are stored as described above.
5. The user is marked as migrated on the server.
6. The user is prompted to set up recovery methods.

Migration is automatic — the AuthCoordinator detects legacy accounts and enters `needs_migration` state without requiring any feature flag.

***

## Key Takeaways

* Private keys are **never stored whole** — they are always split into shares.
* **2-of-4 threshold** — any two shares can reconstruct the key, but one share alone reveals nothing.
* **Server shares are encrypted at rest** with AES-256-GCM using HKDF-derived keys.
* **Multiple recovery methods** provide resilience against device loss.
* **Share versioning** ensures backward compatibility when shares are rotated.
* The entire system is **self-hosted** — no third-party key custody service is involved.


# Account Recovery

How users recover access to their keys when they lose a device

## What is this section about?

This section explains how LearnCard users recover access to their private key when they lose their device, clear their browser storage, or sign in on a new device. Recovery is built on the [SSS key management](/core-concepts/identities-and-keys/key-management-sss) system — specifically, the ability to reconstruct the private key from any two of its four shares.

## Why is this important?

Without recovery, losing a device means permanently losing access to the private key — and with it, the user's entire digital identity. Recovery methods provide a safety net that lets users regain access without compromising security.

***

## When Recovery Is Needed

The **AuthCoordinator** automatically detects when recovery is required. This happens when:

* The user authenticates successfully (e.g., via Firebase), **but**
* No **device share** is found in local storage (IndexedDB)

This typically means the user is on a new device or has cleared their browser data. The coordinator enters the `needs_recovery` state and prompts the user to choose a recovery method.

***

## Recovery Methods

Each method provides a different way to supply the **recovery share** or **email share**. Combined with the **auth share** from the server, these two shares reconstruct the private key.

### Passkey (WebAuthn PRF)

* **How it works:** The recovery share is encrypted using a key derived from the passkey's [PRF (Pseudo-Random Function)](https://w3c.github.io/webauthn/#prf-extension) output. The encrypted share is stored on the server. During recovery, the user authenticates with their passkey, the PRF output is used to decrypt the share, and the key is reconstructed.
* **Security:** Hardware-bound, phishing-resistant. The passkey never leaves the authenticator device.
* **Best for:** Users with platform authenticators (Touch ID, Face ID, Windows Hello) or hardware security keys (YubiKey).
* **Limitation:** Requires a browser and device that supports WebAuthn PRF. Not available on all platforms.

### Recovery Phrase

* **How it works:** The recovery share is encoded as a BIP39-style mnemonic. The user writes down the phrase and stores it somewhere safe. During recovery, the user enters the phrase, which is decoded back into the share.
* **Security:** As secure as the user's physical storage of the phrase. Anyone who obtains the phrase can use it for recovery.
* **Best for:** Users who want an offline, hardware-independent backup.
* **Limitation:** Requires the user to accurately transcribe and safeguard the phrase.

### Backup File

* **How it works:** The recovery share is encrypted with a **user-chosen password** using Argon2id for key derivation and AES-GCM for encryption. The result is packaged as a downloadable JSON file. During recovery, the user uploads the file and enters their password.
* **Security:** Protected by the strength of the user's password. Argon2id provides resistance against brute-force attacks.
* **Best for:** Users who want a portable, password-protected backup they can store in cloud storage or on a USB drive.
* **Limitation:** If the user forgets the password, the backup file is useless.

### Email Backup

* **How it works:** The encrypted backup share is sent to the user's **verified recovery email** address. During recovery, the user retrieves the share from their email and provides it to the app.
* **Security:** Depends on the security of the user's email account. The share is encrypted before being sent.
* **Best for:** Users who want a "set and forget" backup that's always accessible via email.
* **Limitation:** Requires a verified recovery email. Email accounts can be compromised.

***

## Recovery Flow

```mermaid
flowchart TD
    A[User logs in on new device] --> B{Device share found?}
    B -- Yes --> C[Combine device + auth share → ready]
    B -- No --> D[Enter recovery mode]
    D --> E[Show available recovery methods]
    E --> F{User chooses method}
    F -- Passkey --> G[Authenticate with passkey → decrypt share]
    F -- Phrase --> H[Enter recovery phrase → decode share]
    F -- Backup File --> I[Upload file + enter password → decrypt share]
    F -- Email Backup --> J[Retrieve share from email → decrypt share]
    G & H & I & J --> K[Combine chosen recovery share + auth share → reconstruct key]
    K --> L[Generate new device share for this device]
    L --> M[Re-split key → store fresh shares]
    M --> N[Ready]
```

After recovery:

1. The private key is reconstructed from the recovery share + auth share.
2. A **new device share** is created and stored locally.
3. The key is **re-split** so that all four shares are refreshed.
4. Share versioning on the server ensures that existing recovery methods remain valid against their original share version.

***

## Setting Up Recovery Methods

Users are prompted to set up recovery methods after their initial key setup. A **RecoveryBanner** appears on the main app screen until at least one method is configured.

Users can manage their recovery methods from the **Account Recovery** section in their profile settings, where they can:

* Add a passkey
* Generate a recovery phrase
* Download a backup file
* Add and verify a recovery email

Multiple methods can be active simultaneously for maximum resilience.

***

## Recovery Email Verification

Before a recovery email can be used, it must be verified:

1. The user enters their desired recovery email address.
2. A **6-digit verification code** is sent to that address.
3. The user enters the code to confirm ownership.
4. Once verified, the recovery email is stored (masked for privacy) and can be used for email backup.

***

## Key Takeaways

* Recovery is needed when a user logs in but has **no device share** (new device or cleared storage).
* **Four recovery methods** are available: passkey, recovery phrase, backup file, and email backup.
* Any recovery method + the server's auth share = full key reconstruction.
* After recovery, the key is **re-split** with a new device share for the current device.
* Users should set up **at least one recovery method** to avoid permanent key loss.
* Multiple recovery methods can coexist for added resilience.


# Cross-Device Login (QR)

How users securely transfer key access to a new device via QR code

## What is this section about?

This section explains how LearnCard enables users to log in on a new device by scanning a QR code (or entering a short numeric code) from an already-authenticated device. This flow securely transfers the **device share** from the old device to the new one without the server ever seeing the share in plaintext.

## Why is this important?

When a user signs in on a new device, they normally need to use a [recovery method](/core-concepts/identities-and-keys/account-recovery) to reconstruct their key. QR login provides a faster alternative — if the user has another device already logged in, they can transfer the device share directly, skipping the recovery flow entirely.

***

## How It Works

The QR login flow uses **ephemeral ECDH (Elliptic Curve Diffie-Hellman) key exchange** to establish an encrypted channel between two devices. The server acts only as a relay — it never sees the device share in plaintext.

### Flow Diagram

```
Device B (new)                  Server (Redis)               Device A (logged in)
     │                              │                              │
     │── createSession(pubKey) ────▶│                              │
     │◀── sessionId + shortCode ────│                              │
     │                              │                              │
     │    (display QR code or       │                              │
     │     short numeric code)      │                              │
     │                              │                              │
     │                              │◀── scan QR / enter code ─────│
     │                              │                              │
     │                              │◀── approve(encryptedShare) ──│
     │                              │                              │
     │── poll for payload ─────────▶│                              │
     │◀── encryptedPayload ─────────│                              │
     │                              │                              │
     │  (decrypt locally with       │                              │
     │   ephemeral private key)     │                              │
     │                              │                              │
     │  ✓ Device share received     │                              │
```

### Step by Step

1. **Device B** (the new device) generates an **ephemeral ECDH key pair** and sends the public key to the server, creating a session.
2. The server returns a **session ID** and a **short numeric code** (6 digits). Device B displays these as a QR code and/or a typeable code.
3. **Device A** (the already-logged-in device) scans the QR code or the user types the short code. Device A looks up the session and retrieves Device B's ephemeral public key.
4. Device A computes the **shared secret** using ECDH (its own key + Device B's public key), encrypts the device share with AES-GCM using the shared secret, and posts the encrypted payload to the server.
5. Device B **polls** the server for the encrypted payload. Once it arrives, Device B computes the same shared secret (its own private key + Device A's public key) and decrypts the device share.
6. Device B now has the device share. Combined with the auth share from the server, it can reconstruct the private key.

***

## Security Properties

| Property                   | Guarantee                                                                                                 |
| -------------------------- | --------------------------------------------------------------------------------------------------------- |
| **End-to-end encryption**  | The device share is encrypted with a shared secret derived via ECDH. The server only relays opaque bytes. |
| **Ephemeral keys**         | A new key pair is generated for each session. No long-lived key material is exchanged.                    |
| **Short-lived sessions**   | Sessions are stored in Redis with a short TTL and are automatically evicted.                              |
| **No plaintext on server** | The server never has access to the ECDH private key and cannot decrypt the payload.                       |
| **One-time use**           | Once a session is approved, it cannot be reused.                                                          |

***

## Push Notifications

To improve the user experience, Device B can optionally send a **push notification** to Device A, prompting the user to open the QR approver flow. This is fire-and-forget — if the notification fails, the user can still manually open the app on Device A.

***

## Server Routes

The QR login relay is implemented as a set of routes on the `lca-api` server:

| Route                                   | Method | Purpose                                               |
| --------------------------------------- | ------ | ----------------------------------------------------- |
| `/qr-login/session`                     | POST   | Create a session with Device B's ephemeral public key |
| `/qr-login/session/{lookup}`            | GET    | Look up a session by ID or short code                 |
| `/qr-login/session/{sessionId}/approve` | POST   | Post the encrypted share payload (Device A)           |
| `/qr-login/notify`                      | POST   | Send a push notification to the user's other devices  |

All session data lives in **Redis** with short TTLs. No session data is persisted to a database.

***

## When to Use QR Login vs. Recovery

| Scenario                                     | Recommended approach                                             |
| -------------------------------------------- | ---------------------------------------------------------------- |
| User has another device nearby and logged in | **QR Login** — fastest path, no recovery method needed           |
| User lost their only device                  | **Recovery** — use passkey, phrase, backup file, or email backup |
| User is setting up for the first time        | Neither — the key is generated fresh                             |

***

## Key Takeaways

* QR login lets users transfer their device share to a new device **without the server seeing the share**.
* The transfer uses **ephemeral ECDH** for end-to-end encryption between the two devices.
* Sessions are **short-lived** (Redis TTL) and **one-time use**.
* QR login is an **alternative to recovery** — it's faster when the user has another authenticated device available.


# Network Profiles

LearnCloud Network provides rich DID-based user profiles. They are the foundation for user identity and relationship management in the Network. The LearnCloud Network implements functionality for creating and managing user profiles, establishing connections between users, and managing authentication and authorization.

## Profile Types and Data Model

The LearnCard Network supports three distinct types of profiles, each serving different use cases while sharing the same underlying data model.

<table data-header-hidden data-full-width="true"><thead><tr><th width="162.50115966796875">Profile Type</th><th>Description</th><th>When to Use / Key Use Cases</th></tr></thead><tbody><tr><td>Regular Profile</td><td><p>Represents an individual person directly using an application or service. This is the standard type for most human users.</p><p><code>isServiceProfile = false</code></p></td><td>Ideal for individual human users. Enables direct self-management of personal data and settings. Suits typical interactive use, like logging into apps or websites.</td></tr><tr><td>Service Profile</td><td>Represents a non-human actor, like an application, script, or automated process, that needs to interact with APIs or services programmatically. <code>isServiceProfile = true</code></td><td>For automated systems or applications needing API access. Used for backend processes, server-to-server communication, and automated tasks. Assigns specific, often limited, permissions to software components.</td></tr><tr><td>Managed Profile</td><td>A profile whose activities, permissions, or data are overseen or controlled by another designated "Manager Profile." This supports delegation and oversight. They are created using the <code>createManagedServiceProfile</code></td><td>For accounts requiring supervision (e.g., a child's profile managed by a guardian). Represents organizational sub-units (like a department) controlled by a parent organizational account. When an entity needs to operate with permissions granted and managed by a separate authority.</td></tr></tbody></table>

```mermaid
graph TB
    subgraph "Profile Types"
        RP["Regular Profile"]
        SP["Service Profile"]
        MSP["Managed Service Profile"]
    end

    subgraph "Authentication"
        UD["User DID"]
        GD["Generated DID"]
    end

    subgraph "Management"
        MP["Manager Profile"]
    end

    UD --> RP
    UD --> SP
    GD --> MSP
    MP --->|"manages"| MSP

    style RP fill:white
    style SP fill:white
    style MSP fill:white
    style UD fill:white
    style GD fill:white
    style MP fill:white
```


# Signing Authorities

## **What is a Signing Authority and Why Use It?**

Imagine your organization (the Owner) wants to allow a specific department's application or a trusted partner service to issue "Course Completion" badges on its behalf. You wouldn't want to give that application your organization's main private key.

A **Signing Authority** is a separate service with its own unique identity and keys. The Owner can authorize this Signing Authority to issue specific credentials on their behalf. When a credential needs to be issued:

1. The Owner (or an application acting for the Owner) requests the issuance via the LearnCloud Network.
2. The request specifies that the designated, registered Signing Authority should perform the actual cryptographic signing.
3. The LearnCloud Network contacts your AWS-hosted Signing Authority.
4. Your Signing Authority signs the credential using *its own keys*.
5. The resulting Verifiable Credential (VC) still shows the Owner as the `issuer`, but the cryptographic `proof` indicates it was generated by your AWS-hosted Signing Authority, as authorized by the Owner.

**Benefits:**

* **Enhanced Security:** The Owner's primary keys are not shared with the services performing routine issuance.
* **Delegation:** Safely delegate issuance capabilities to other applications or services.
* **Cloud Scalability & Reliability:** Leverage AWS infrastructure for your signing operations.
* **Scoped Permissions:** (Future capability) Potentially limit what types of credentials a specific authority can issue.

Signing Authorities allow third-party services to issue credentials on behalf of profiles. This enables credential issuance without requiring direct access to private keys.

```mermaid
graph TD
    subgraph "LearnCloud Network API"
        brain["LearnCloud Network API"]

        subgraph "Signing Authority Flow"
            register["registerSigningAuthority()"]
            issue["issueCredentialWithSigningAuthority()"]
            send["sendBoostViaSigningAuthority()"]
            write["writeToContractViaSigningAuthority()"]
        end
    end

    subgraph "External Service"
        endpoint["Signing Authority Endpoint"]
        keys["Private Keys"]
    end

    register -->|"stores metadata"| brain
    issue -->|"requests signature"| endpoint
    send -->|"requests signature"| endpoint
    write -->|"requests signature"| endpoint

    endpoint -->|"signs with"| keys
```


# Trust Registries

Understanding Trust Registries: Who to Trust in the Digital Credential World

## **What is this section about?**

In the new world of digital, verifiable credentials, you might receive a digital certificate or badge from an organization. But how do you (or the application you're using) know if that organization is who they say they are, or if they are generally considered legitimate? This is where "Trust Registries" come into play. This explainer will help you understand what Trust Registries are, why they are a crucial piece of the digital trust puzzle, and how systems like LearnCard work with different types of registries, each with its own rules.

### **Why is this important for you to understand?**

Trust is the cornerstone of any credentialing system. Whether it's a university degree, a professional license, or a community badge, its value depends on trusting the entity that issued it. In the digital realm, Trust Registries provide a way to help establish that trust, making digital credentials more reliable and useful. Understanding them helps you appreciate how we determine the legitimacy of digital information.

### **What you'll learn:**

* What a Trust Registry is in simple terms.
* Why Trust Registries are essential for verifying digital credentials.
* How multiple "federated" registries can work together.
* The importance of governance for each registry.
* The main kinds of registries LearnCard interacts with, like JSON-based and OIDF registries.

## What is a Trust Registry?

At its core, a **Trust Registry** is essentially a curated list. It's a published collection of digital identifiers (specifically **Decentralized Identifiers, or DIDs**) that belong to organizations or individuals who are recognized or vouched for by the entity that maintains the registry.

Think of it like:

* An **"approved vendor list"** for a company.
* A **directory of accredited universities** published by an education board.
* A **"Blue Checkmark" system** for certain types of online profiles, but managed by specific communities or organizations.

When an application encounters a DID (for example, the DID of an issuer on a digital credential), it can check against one or more Trust Registries. If the DID is listed, it provides some level of assurance that the issuer is known and recognized by the maintainer of that registry.

The information typically found in a registry entry for a DID includes details like the organization's name, website, and location. For example, a simple JSON-based registry might look like this snippet from `registries.learncard.com/trusted/registry.json`:

```json
{
    "registry": {
        "did:web:network.learncard.com:users:smartresume": {
            "name": "SmartResume",
            "location": "Little Rock, AR 72201",
            "url": "https://www.smartresume.com/"
        },
        "did:web:scoutnetwork.org": {
            "name": "ScoutPass",
            "location": "Kuala Lumpur, Malaysia",
            "url": "https://pass.scout.org/"
        }
        // ... more entries
    }
}

```

Here, `did:web:network.learncard.com:users:smartresume` is the digital identifier, and the associated information tells us it's "SmartResume."

## Why Do Trust Registries Matter? The Problem of Trust in a Decentralized World

Verifiable Credentials and DIDs are powerful because they can be decentralized – anyone can create a DID, and anyone can issue a credential. But this openness also presents a challenge: if you receive a digital credential claiming to be from "State University" with an issuer DID like `did:example:12345`, how do you know it's the *real* State University and not an imposter?

Trust Registries help address this. They don't provide absolute, universal proof (as trust is complex), but they offer a crucial layer of verification by allowing you to check if an issuer's DID is recognized by one or more known organizations or communities. They help answer the question: *"Is this issuer known and generally considered legitimate according to the criteria of a particular group or authority?"*

## Federated Registries: A Network of Trust, Not a Single Source

It's important to understand that there isn't just *one* global, all-encompassing Trust Registry that everyone uses for everything. That would recreate a centralized system, which DIDs and VCs often aim to move beyond.

Instead, we have a system of **Federated Registries**. This means:

* **Multiple, Independent Registries:** Different organizations, consortiums, industries, or communities can maintain their own Trust Registries, each with its own focus, criteria for inclusion, and level of authority.
* **LearnCard Knows Many:** Systems like LearnCard can be configured to be aware of and consult multiple such registries. For example, `registries.learncard.com/known-did-registries.json` lists several registries that LearnCard can check:

  ```json
  [
      {
          "name": "LEF Member Registry",
          "type": "dcc-legacy", 
          "governanceUrl": "https://learningeconomy.io",
          "url": "https://registries.learncard.com/trusted/registry.json"
      },
      {
          "name": "Credential Engine Test Issuer Registry",
          "type": "oidf",
          "governanceUrl": "https://credentialengine.org/",
          "trustAnchorEC": "https://test.issuerregistry.credentialengine.org/oidfed/.well-known/openid-federation"
      },
      // ... and more
  ]

  ```
* **Contextual Trust:** This federated approach allows for contextual trust. A university might be listed in an educational consortium's registry, while a software vendor might be in a technology alliance's registry. The "trust" you infer depends on your trust in the registry maintainer itself.
* **Order Can Matter:** If a DID appears in multiple registries that your system checks, the system might prioritize information from one registry over another based on a predefined order of authoritativeness (e.g., preferring a government-maintained registry over a community-maintained one for certain types of credentials).

Think of it like professional accreditations: a doctor might be accredited by a national medical board, a lawyer by a bar association, and an architect by an architectural institute. Each registry (accreditation body) is independent but contributes to a broader web of trust.

## Governance: Who Decides Who's In?

Since anyone can set up a list, what makes a Trust Registry trustworthy itself? The answer lies in its **governance**.

* **Rules of the Road:** Each Trust Registry operates under its own set of rules, policies, and procedures that determine:
  * Who can be listed.
  * What criteria must be met for inclusion.
  * How information is verified before being added.
  * How often entries are reviewed or updated.
  * How disputes or inaccuracies are handled.
* **Transparency is Key:** The credibility of a registry often depends on the transparency and rigor of its governance model. Many registries provide a `governanceUrl` (as seen in the examples above) where you can learn about these rules.
* **Trust is Transitive (to a degree):** When you decide to rely on a particular Trust Registry, you are effectively placing some trust in its governance model and the entity that maintains it.

So, when a system checks a DID against a registry, the "trust" isn't just about the DID being present; it's about the DID meeting the standards set forth by that registry's specific governance.

## Types of Trust Registries LearnCard Interacts With

LearnCard is designed to work with different types of Trust Registries to provide flexibility and broad interoperability. The main kinds you'll see referenced are:

1. **JSON Registries (e.g., "dcc-legacy" type):**
   * **What they are:** These are often straightforward, publicly accessible files in JSON (JavaScript Object Notation) format. They typically contain a list of DIDs, and for each DID, associated metadata like the organization's name, website, and location.
   * **Example:** The `registries.learncard.com/trusted/registry.json` file is a good example. It's a simple list that applications can fetch and parse.
   * **Simplicity:** Their main advantage is simplicity in creation and consumption.
2. **OIDF Registries (OpenID Federation):**
   * **What they are:** These registries use a more dynamic and standardized set of protocols based on the OpenID Federation specification. Instead of a static list, entities in an OIDF registry publish their own "Entity Configuration" (EC) at a well-known URL. This EC contains their metadata and information about how they relate to other entities in the federation.
   * **How it works:** A system like LearnCard starts with a "Trust Anchor" (a trusted starting point, often an Entity Configuration URL like `https://registry.dcconsortium.org/.well-known/openid-federation` for the "DCC Member Registry"). From this anchor, it can discover other entities and their metadata within that federation.
   * **Dynamic & Standardized:** OIDF provides a more robust and scalable framework for establishing and discovering trust relationships, especially in larger, more complex ecosystems.

## How LearnCard Uses Trust Registries (A Glimpse)

When LearnCard encounters a Verifiable Credential, it can use the issuer's DID to query the Trust Registries it knows about.

* If the issuer's DID is found in a trusted registry, LearnCard can retrieve additional information about the issuer (like their official name and website).
* This helps in the verification process by providing more context and confidence about the issuer's identity. It doesn't automatically mean the credential's *content* is true, but it helps confirm the *source* is recognized.

For example, if you receive a credential from `did:web:some-university.edu`, LearnCard might check a registry of educational institutions. If that DID is listed with the name "Some University" and a link to their official website, it adds a layer of assurance.

## Conclusion: Trust is Contextual and Layered

Trust Registries are a vital component in making the decentralized world of Verifiable Credentials practical and trustworthy. They provide a mechanism for recognizing legitimate actors and verifying their identities.

{% hint style="info" %}

## **Key takeaways**

* Trust Registries are curated lists of recognized DIDs.
* They help verify the legitimacy of issuers of digital credentials.
* The digital world uses a **federated** model, meaning many independent registries exist, each with its own **governance** and criteria.
* LearnCard can interact with various types of registries, including simple JSON lists and more dynamic OIDF-based federations.
  {% endhint %}

Ultimately, the "trust" derived from a registry is always contextual—it depends on the credibility and rules of the registry itself. By understanding how they work, you can better appreciate the layers of verification that contribute to building a more trustworthy digital future.


# Holder Continuity

How holders can export, verify, and move LearnCard wallet data

LearnCard is designed so a holder can keep control of the artifacts that define their identity: private key material, DIDs, Verifiable Credentials, Verifiable Presentations, consent records, and status snapshots.

## Current custody model

LearnCard's AuthCoordinator uses Shamir Secret Sharing with a **2-of-4 threshold**. The private key is split into four shares:

| Share          | Where it lives                                            | Purpose                           |
| -------------- | --------------------------------------------------------- | --------------------------------- |
| Device share   | Local IndexedDB                                           | Fast same-device login            |
| Auth share     | LearnCard API server, encrypted at rest                   | Authenticated recovery and login  |
| Recovery share | Passkey, phrase, or password backup                       | User-controlled recovery          |
| Email share    | Verified recovery email backup, encrypted before delivery | Optional additional recovery path |

Any two shares can reconstruct the key. One share alone is not useful.

## What the holder controls

The holder continuity export includes:

* DID key material as encrypted JWKs and the private key seed.
* A recovery phrase derived from the current SSS recovery share.
* The primary DID document and locally derivable DID variants.
* W3C Verifiable Credential and Verifiable Presentation payloads exactly as resolved from the holder's wallet index.
* Encrypted LearnCloud index metadata, including URI, record ID, category, and title where available.
* ConsentFlow contracts, terms, statuses, and transaction history returned by the authenticated holder export metadata route.
* Status-list credential snapshots when the status URL is publicly fetchable during export.

The export preserves issuer-signed JSON. It does not normalize proofs or rewrite credential contents.

## Zero-cooperation exit path

A holder who can still access their wallet can export a bundle with the LearnCard CLI:

```js
const password = await getLearnCardBundlePassword();

await exportLearnCardBundle(learnCard, {
    out: './learncard-export.zip',
    password,
});
```

The CLI also supports `await exportLearnCardBundle({ ...options })`, which exports the default `learnCard` wallet created at startup.

The underlying bundle helpers live in `@learncard/holder-continuity`:

```ts
import {
    exportLearnCardBundle,
    importLearnCardBundle,
    restoreLearnCardFromBundle,
} from '@learncard/holder-continuity';
```

After the ZIP is created, the holder can decrypt and inspect it with only the bundle password and public tooling. The readable `manifest.json` lists every payload and SHA-256 hash, while sensitive payloads and LearnCloud index records remain encrypted. Encrypted files use Argon2id and AES-GCM through the same `@learncard/sss-key-manager` password envelope used for account backup files.

A fresh LearnCard wallet can import the bundle without help from the original LearnCard account:

```js
const freshWallet = await initLearnCard({ seed: '0'.repeat(64) });
await importLearnCardBundle('./learncard-export.zip', {
    password,
    wallet: freshWallet,
    verifyBeforeImport: true,
});
```

The original wallet identity can also be reconstructed directly from the exported seed:

```js
const restoredWallet = await restoreLearnCardFromBundle('./learncard-export.zip', {
    password,
    init: { network: true, didkit: 'node' },
});
```

Import writes to the target wallet. Use `verifyBeforeImport: true` when the target wallet can verify VCs/VPs before upload; otherwise, only import bundles from sources you trust.

Status-list snapshots are fetched only from public HTTPS URLs, with timeout and response-size limits, to avoid turning holder-supplied credentials into server-side fetches to private networks.

Restore passes the exported `key-private-seed` to `initLearnCard(...)`. It recreates the original key and DID; it does not upload payloads or recreate index records. Use restore when you want the original wallet identity back, and use import when you want to copy credentials into another wallet.

## Continuity of service

The exported artifacts are based on public standards and readable formats:

* DIDs and DID Documents are JSON.
* Verifiable Credentials and Verifiable Presentations are W3C VC/VP JSON payloads.
* JSON-LD proofs, JWT VCs, and BitstringStatusList or StatusList2021 credentials can be verified by non-LearnCard libraries that support those suites and contexts.
* Consent records are JSON snapshots with contract terms and transaction history.
* The outer container is a normal ZIP file; encryption is per-payload JSON, not proprietary ZIP encryption.

If LearnCard services are unavailable, the bundle remains useful for independent verification, audit, and migration work.

## What is not exported

Some LearnCard network behavior is service-specific and cannot be fully reconstructed from holder artifacts alone:

* Boost authoring semantics and issuer-side template management.
* Claim hooks, notification queues, inbox delivery state, and server-only activity feeds.
* Network graph edges that are not represented in a credential, consent record, or transaction.
* LearnCard convenience relationships such as profile recommendations or app-specific UI grouping.

Some of this can be recreated from credentials and consent records. For example, a credential's issuer, subject, proof, and status fields remain available. Server-only state that was never written into a holder-controlled artifact is not portable.

## Sensitivity warning

A holder continuity export is personal data. It can include identifiers, credentials, consent history, transaction timestamps, and key material. Store it like a password vault backup, rotate the export if it is exposed, and treat status-cache files as point-in-time snapshots rather than live revocation guarantees.

Critically, the export contains the wallet's **full raw private-key seed** (encrypted with the bundle password). The live wallet normally protects this key with the 2-of-4 SSS threshold described above, where no single share is enough to reconstruct it. The bundle does not preserve that threshold: the exported seed alone is sufficient to take full control of the identity, so the bundle password is the only thing protecting it. The exported recovery phrase is derived from the current recovery share for reference and is not independently sufficient to recover the key — restore uses the exported seed.

## Compatibility with other wallets

No third-party wallet imports the LearnCard ZIP bundle directly today. External wallets receive credentials through protocols (OID4VCI, DIDComm) or through SDK-level JSON import, not by reading `manifest.json` and decrypting entries.

In practice, the portable path is: decrypt the bundle, take the individual Verifiable Credential and Presentation JSON payloads, and hand them to the target wallet through whatever import path that wallet documents. Keep the original ZIP as an audit backup.

The table below reflects what each vendor's public docs describe at the time of writing. Treat it as a starting point — verify against current vendor docs, and do not assume a wallet supports the LearnCard bundle unless it has been tested against the ZIP format.

| Wallet / toolkit                                                                                                     | Raw ZIP import | Credential JSON import (after decrypt) | OID4VCI receive | DIDComm receive |
| -------------------------------------------------------------------------------------------------------------------- | -------------- | -------------------------------------- | --------------- | --------------- |
| [Spruce / SpruceKit](https://www.sprucekit.dev/sprucekit-mobile/sprucekit-mobile-sdk/core-components/credentialpack) | No             | Yes — `CredentialPack.addJsonVc(...)`  | Yes             | Not documented  |
| [Sphereon Wallet / SSI SDK](https://ssisdk.docs.sphereon.com/mobile-wallet/receiving-credentials)                    | No             | Not documented                         | Yes             | Not documented  |
| [MATTR](https://learn.mattr.global/docs/holding/credential-claiming-overview)                                        | No             | Not documented                         | Yes             | Not documented  |
| [Paradym Wallet](https://docs.paradym.id/api-and-dashboard/integrating-with-a-holder-wallet/paradym-wallet)          | No             | Not documented                         | Yes             | Not documented  |

## Related pages

* [LearnCard CLI](/sdks/learncard-cli)
* [Key Management (SSS)](/core-concepts/identities-and-keys/key-management-sss)


# Credentials & Data

Verifiable Credential structure, Schemas, Data Models, Boosts

## **What is this section about?**

This section focuses on the *information* itself—the digital records that are created, shared, and verified within the LearnCard system. We'll look closely at the structure of Verifiable Credentials (VCs), how their format is defined using Schemas, other important Data Models, and unique constructs like "Boosts."

### **Why is this important for you to understand?**

The core value of LearnCard revolves around its ability to manage and exchange trustworthy, verifiable information. To build meaningful applications, you need to know how this information is structured, what it represents, and how to interpret it. Understanding VCs, Schemas, and concepts like "Boosts" is essential for creating, issuing, presenting, and verifying these digital assets correctly. This is like understanding the layout and content of an official certificate or a detailed project portfolio.

### **What you'll learn:**

You'll explore the anatomy of a Verifiable Credential, how Schemas ensure consistency, how different pieces of data relate to each other through Data Models, and the specific role and structure of "Boosts."

***

### 🎓 Verifiable Credentials (VCs)

Verifiable Credentials are portable digital statements—like a diploma, badge, or license—that anyone can issue and anyone can verify.

* **Issuer**: The party who creates the credential (e.g. a school, platform, employer).
* **Holder**: The person or agent who owns the credential.
* **Verifier**: Anyone who needs to check that the credential is legit.

LearnCard uses the [W3C VC standard](https://www.w3.org/TR/vc-data-model/) to ensure global interoperability and verification.

***

### 📦 Verifiable Presentations (VPs)

A **Verifiable Presentation (VP)** is a bundle of one or more credentials that a user shares with another party.

* Think of it as a "credential folder" you can pass around
* Can be generated dynamically with selective fields
* Fully verifiable using DIDs and cryptographic proofs

***

### 🔓 Open Standards

Interoperability is built-in. LearnCard supports:

* **Open Badges v3 (OBv3)**
* **Comprehensive Learner Record (CLR)**
* **Learning Tools Interoperability (LTI)**
* **Learning and Employment Records (LER)**
* **Learner Information Framework (LIF)**

***

### ⚡ Boosts

A **Boost** is a superpowered VC. It follows OBv3 and W3C standards but adds:

* **Display metadata**: Control how credentials appear in wallets
* **Governance rules**: Enforce who can issue what
* **Network validation**: Confirm issuance rules were followed
* **Attachments**: Include files, PDFs, or resources

Think of Boosts as **VC+**—fully standards-compliant but better for real-world apps.


# Verifiable Credentials (VCs)

Verifiable Credentials are a W3C standard for expressing credentials in a way that is cryptographically secure, privacy-respecting, and machine-verifiable. They enable trusted digital claims about subjects.

### Verifiable Credential Data Model <a href="#credential-data-model" id="credential-data-model"></a>

LearnCard implements the W3C Verifiable Credentials Data Model, with support for both VC 1.0 and VC 2.0 formats. The core data types are defined using Zod validators.

#### Credential Structure <a href="#credential-structure" id="credential-structure"></a>

```mermaid
graph 
    subgraph "Verifiable Credential"
        VC["Verifiable Credential"]
        VC --> Context["@context"]
        VC --> ID["id"]
        VC --> Type["type"]
        VC --> Issuer["issuer"]
        VC --> IssuanceDate["issuanceDate"]
        VC --> Subject["credentialSubject"]
        VC --> Proof["proof"]

        Subject --> SubjectID["id (DID of subject)"]
        Subject --> Claims["Claims (achievements, skills, etc.)"]

        Proof --> ProofType["type (e.g., Ed25519Signature2020)"]
        Proof --> Created["created"]
        Proof --> VM["verificationMethod"]
        Proof --> Purpose["proofPurpose"]
        Proof --> JWS["jws (signature)"]
    end
```

| Component           | Description                              | Required |
| ------------------- | ---------------------------------------- | -------- |
| `@context`          | JSON-LD contexts defining the vocabulary | Yes      |
| `id`                | Unique identifier for the credential     | No       |
| `type`              | Array of credential types                | Yes      |
| `issuer`            | Entity that issued the credential        | Yes      |
| `credentialSubject` | Entity the credential is about           | Yes      |
| `proof`             | Cryptographic proof of authenticity      | Yes      |

Version differences:

* VC 1.0 uses `issuanceDate` and `expirationDate`
* VC 2.0 uses `validFrom` and `validUntil`

{% @github-files/github-code-block url="<https://github.com/learningeconomy/LearnCard/blob/942bb5f7/packages/learn-card-types/src/vc.ts#L129-L177>" %}

<br>

## Verifiable Presentations (VPs) <a href="#verifiable-presentations-vps" id="verifiable-presentations-vps"></a>

Verifiable Presentations allow holders to bundle and selectively disclose credentials:

```mermaid
graph 
    subgraph "Verifiable Presentation"
        VP["Verifiable Presentation"]
        VP --> VPContext["@context"]
        VP --> VPID["id"]
        VP --> VPType["type"]
        VP --> Holder["holder"]
        VP --> VCs["verifiableCredential[]"]
        VP --> VPProof["proof"]

        VCs --> VC1["Credential 1"]
        VCs --> VC2["Credential 2"]

        VPProof --> VPProofType["type"]
        VPProof --> VPCreated["created"]
        VPProof --> VPVM["verificationMethod"]
        VPProof --> VPPurpose["proofPurpose"]
        VPProof --> Challenge["challenge (optional)"]
        VPProof --> Domain["domain (optional)"]
        VPProof --> VPJWS["jws (signature)"]
    end
```


# Credential Lifecycle

A **Verifiable Credential (VC)** is a secure, digital representation of information (claims or attributes) about an entity. VCs are typically controlled by the individual or organization they pertain to. This document outlines the standard lifecycle of a VC—from its creation and issuance to its presentation and verification—as illustrated in the accompanying diagram. Understanding this process is key to recognizing the utility of VCs across various applications.

{% embed url="<https://www.figma.com/board/DPGBfPLlss2K6KmDLCN3ul/LearnCard-Docs?node-id=130-63&t=fk1wywzjUFmakXJE-0>" %}

### Key Roles in the Credential Lifecycle

Several distinct roles are involved in the lifecycle of a Verifiable Credential:

* **Issuer:** An entity responsible for creating, digitally signing, and distributing VCs.
  * *Example:* A university issuing a digital diploma.
* **Holder:** An individual or entity that receives, stores, and manages their VCs. The Holder decides when and with whom to share their credentials.
* **Verifier:** An entity that requests VCs from a Holder and checks them to confirm their authenticity and the validity of the information they contain.
  * *Example:* An employer validating a job applicant's claimed degree.
* **LearnCard & LearnCloud:** The software providing the tools and infrastructure for Issuers to create VCs, Holders to manage them , and Verifiers to check their validity.

### The Credential Lifecycle: Step-by-Step

The following steps describe the typical lifecycle of a Verifiable Credential, as shown in the diagram below:

1. **Issuance (Creation & Signing)**
   * The **Issuer** uses **LearnCard Wallet SDK** to initiate the creation of a new credential (e.g., an "achievement" credential).
   * LearnCard SDK prepares an initial, unsigned version of the credential data.
   * The Issuer then directs the LearnCard SDK to apply a **digital signature** using the Issuer's unique cryptographic key (often associated with their Decentralized Identifier or DID). This signature ensures the credential's authenticity (proof of origin) and integrity (proof it hasn't been tampered with).
   * LearnCard SDK returns the signed Verifiable Credential (VC) to the Issuer.
2. **Delivery and Storage**
   * The **Issuer** delivers the signed VC to the **Holder** (e.g., via LearnCloud Network).
   * The Holder uses **LearnCard App** or another interoperable wallet to securely store the VC in a storage provider (e.g. LearnCloud Storage) which provides a unique link (URI) for referencing the credential.
3. **Indexing (Optional)**
   * To facilitate easier management and retrieval, the **Holder** can use **LearnCard SDK** (e.g., via LearnCloud Storage) to index the credential, linking its identifier to its storage location or metadata.
4. **Presentation**
   * When the **Holder** needs to prove a claim to a **Verifier**, they select the relevant VC(s) from their wallet and presents them to the Verifier (e.g., via LearnCloud Network, by sharing a data file, displaying a QR code, or via a direct application-to-application exchange).
5. **Verification**
   * The **Verifier** receives the VC(s) from the Holder.
   * The Verifier uses **LearnCard SDK** (or an interoperable verification service) to check the VC's validity.
   * LearnCard SDK typically performs several validation steps:
     * Verifying the **digital signature** against the Issuer's public key to ensure authenticity and integrity.
     * Validating the credential's structure and data types against its defined **schema**.
     * Checking **issuance and expiration dates** (if applicable) and [credential status](/core-concepts/credentials-and-data/credential-status-and-bitstring-status-lists).
   * LearnCard SDK then returns a verification result (e.g., valid or invalid) to the **Verifier**.

### Practical Use Cases for Verifiable Credentials

The VC lifecycle enables a wide range of applications by providing a standardized, secure, and user-centric way to issue and verify digital information:

* **Academic & Professional Achievements:** Issuing and verifying digital diplomas, course certificates, skill badges, and professional licenses.
* **Access Control:** Using VCs as digital keys or passes for entry to physical locations, online services, or events (e.g., digital tickets, membership authenticators).
* **Identity & Attribute Verification:** Presenting verified personal attributes for processes like "Know Your Customer" (KYC), age verification, or as digital employee/student IDs, often enabling selective disclosure of information.

**Key advantages of this model include:**

* Increased data security and integrity through cryptography.
* Enhanced user control and privacy over personal information.
* Improved portability of credentials across different systems.
* More efficient and streamlined verification processes.

### Key Terminology

Understanding these terms is helpful when working with Verifiable Credentials:

* **Verifiable Credential (VC):** A digital document containing claims (statements) made by an Issuer about a Holder. It is cryptographically secured and can be independently verified.
* **Issuer:** The entity that creates, digitally signs, and distributes VCs.
* **Holder:** The entity (typically an individual or organization) that possesses and controls their VCs.
* **Verifier:** The entity that requests and validates VCs presented by a Holder to confirm their authenticity and the claims within.
* **LearnCard & LearnCloud:** The software platform providing the necessary infrastructure and tools for VC issuance, storage, management, and verification.
* **Digital Signature:** A cryptographic mechanism used to prove the authenticity (who signed it) and integrity (it hasn't been changed) of a VC, linking it to the Issuer.
* **DID (Decentralized Identifier):** A globally unique, verifiable identifier that does not require a central registration authority. DIDs are often used to identify Issuers and Holders.
* **Schema:** The defined structure, data types, and format for a specific type of credential, outlining what information fields it should contain (e.g., a "Diploma" schema would specify fields for name, institution, degree, etc.).

### Diagram of the Lifecycle

The following diagram illustrates the Verifiable Credential lifecycle described above:

```mermaid
sequenceDiagram
    participant Issuer
    participant Core as "LearnCard Wallet SDK"
    participant Network as "LearnCloud Network API"
    participant Holder
    participant Verifier

    Issuer->>Core: "Create unsigned credential"
    Core-->>Issuer: "Unsigned credential"
    Issuer->>Core: "Issue credential (sign)"
    Core-->>Issuer: "Signed credential (VC)"

    Issuer->>Network: "Send credential to recipient"
    Network->>Holder: "Notify of incoming credential"
    Holder->>Network: "Accept credential"
    Network-->>Holder: "Credential available"

    Holder->>Verifier: "Present credential"
    Verifier->>Core: "Verify credential"
    Core->>Core: "Check signature"
    Core->>Core: "Check expiration"
    Core->>Core: "Validate schema"
    Core-->>Verifier: "Verification result"

    Note over Issuer,Verifier: "Alternative: Direct issuance without network"
    Issuer->>Core: "Issue credential"
    Core-->>Issuer: "Signed credential"
    Issuer->>Holder: "Direct transfer (QR, file, etc.)"
    Holder->>Core: "Store credential"
    Holder->>Verifier: "Present credential"
```




---

[Next Page](/llms-full.txt/1)

