npx agentspec-cli install 8994bcc5-1dae-4cbc-ad7d-fa9f2103f0ec --framework ClaudeCode→ .claude/skills/Elemental API — The Platform Data Source/SKILL.md
# Elemental API — The Platform Data Source
**This app is built on the Lovelace platform.** The Query Server is the
primary data source — use it first for any data needs (entities, news,
filings, sentiment, relationships, events). Do NOT call external APIs
(e.g. sec.gov, Wikipedia) for data that the platform already provides.
The Elemental API provides access to the Lovelace Knowledge Graph through
the Query Server. Use it to search for entities, retrieve properties,
explore relationships, and analyze sentiment. New data sources are added
regularly — use the discovery-first pattern to find what's available.
## Skill Documentation
For endpoint reference, response shapes, and edge cases, **read the
elemental-api skill** in `skills/elemental-api/` (start with `SKILL.md` and
follow the skill’s own structure). Files are copied from
`@yottagraph-app/elemental-api-skill` during `npm install` (see
`skill-packages.json`); if the directory is empty, run `npm install` first —
**run it early** during initial exploration so the skill is available.
## Data model skill
For Lovelace **entity types, properties, relationships, and per-source schemas** (EDGAR, FRED, FDIC, etc.), read the **data-model skill** in `skills/data-model/`. Start with `SKILL.md`, then `overview.md` and the source-specific folders. These files are copied from `@yottagraph-app/data-model-skill` during `npm install` (same postinstall; both skills are listed in `skill-packages.json`).
## Client Usage
All API calls go through `useElementalClient()` from `@yottagraph-app/elemental-api/client`.
Auth tokens and base URL are configured automatically by the `elemental-client` plugin.
```typescript
import { useElementalClient } from '@yottagraph-app/elemental-api/client';
const client = useElementalClient();
const results = await client.getNEID({ entityName: 'Apple', maxResults: 5 });
const report = await client.getNamedEntityReport(results.neids[0]);
const schema = await client.getSchema();
```
Types are also imported from the client:
```typescript
import type { NamedEntityReport, GetNEIDResponse } from '@yottagraph-app/elemental-api/client';
```
### Client Method Quick Reference
All methods return data directly and throw on non-2xx responses.
**Entity search and lookup:**
| Method | Signature | Purpose |
|---|---|---|
| `getNEID` | `(params: { entityName, maxResults?, includeNames? })` | Lookup entity by name |
| `findEntities` | `(body: FindEntitiesBody)` | Expression-based search (see `find.md`) |
| `getNamedEntityReport` | `(neid: string)` | Entity details (name, aliases, type) |
| `getEntityDetails` | `(neid: string)` | Alias for entity reports |
**Properties and schema:**
| Method | Signature | Purpose |
|---|---|---|
| `getSchema` | `()` | All entity types (flavors) and properties (PIDs) |
| `getPropertyValues` | `(body: { eids: string, pids: string })` | Property values (eids/pids are JSON-stringified arrays!) |
| `summarizeProperty` | `(pid: number)` | Summary stats for a property |
**Relationships and graph:**
| Method | Signature | Purpose |
|---|---|---|
| `getLinkedEntities` | `(neid, params?: { entity_type?, link_type? })` | Linked entities (person/org/location only) |
| `getLinks` | `(sourceNeid, targetNeid, params?)` | Links between two specific entities |
| `getLinkCounts` | `(sourceNeid, targetNeid)` | Link counts between entities |
| `getNeighborhood` | `(centerNeid, params?)` | Neighboring entities |
| `getGraphLayout` | `(centerNeid, params?)` | Graph layout for visualization |
**News, events, and sentiment:**
| Method | Signature | Purpose |
|---|---|---|
| `getArticle` | `(artid: string)` | Article by ID |
| `getArticleText` | `(artid: string)` | Article full text |
| `getEvent` | `(eveid: string)` | Event by ID |
| `getEventsForEntity` | `(params: { neid, startTime?, endTime? })` | Events involving an entity |
| `getMentions` | `(params: { neid, startTime?, endTime? })` | Mention codes for entities |
| `getMentionCounts` | `(params: { neid, ... })` | Bucketed mention counts |
| `getNamedEntitySentiment` | `(neid: string)` | Sentiment for an entity |
**Other:**
| Method | Signature | Purpose |
|---|---|---|
| `getHealth` | `()` | Health check |
| `getStatus` | `()` | Server status and capabilities |
| `adaMessage` | `(body: AdaMessageBody)` | Ada AI chat |
## Discovery-First Pattern
The knowledge graph contains many entity types and properties, and new datasets
are added regularly (e.g. Edgar filings, financial data). Do NOT hardcode entity
types or property names. Instead, discover them at runtime:
1. **Get the schema** — `client.getSchema()` returns all entity types (flavors)
and properties (PIDs) available in the system. See `schema.md`.
The schema response contains:
- **Flavors** (entity types): Company, Person, GovernmentOrg, etc.
Each flavor has a numeric ID and a human-readable name.
- **PIDs** (properties): name, country, industry, lei_code, etc.
Each PID has a type (`data_str`, `data_int`, `data_nindex`, etc.).
- Properties with type `data_nindex` are references to other entities —
resolve them with another `getPropertyValues` call.
Use flavor names in `findEntities()` expressions and PID names in
`getPropertyValues()`.
2. **Search with expressions** — `client.findEntities()` uses a JSON expression
language to search by type, property value, or relationship. See `find.md`.
3. **Get property values** — `client.getPropertyValues()` fetches property data
for specific entities.
This pattern lets agents work with any dataset without needing hardcoded
knowledge of what's in the graph.
## API Gotchas
### `getSchema()` response is nested — WILL crash if you don't handle it
The generated TypeScript types suggest `response.properties` and
`response.flavors` exist at the top level. **They don't.** The API nests
them under `response.schema`. This mismatch between types and reality
causes `Cannot read properties of undefined` every time.
```typescript
// WRONG — will crash at runtime despite TypeScript compiling fine:
const res = await client.getSchema();
const props = res.properties; // undefined!
// CORRECT — always access through .schema:
const res = await client.getSchema();
const properties = res.schema?.properties ?? (res as any).properties ?? [];
const flavors = res.schema?.flavors ?? (res as any).flavors ?? [];
```
The `(res as any).properties` fallback is there in case the API is ever
fixed to match the types. Use this pattern every time.
### `getNamedEntityReport()` response is nested under `.report`
Same problem as `getSchema()`. The TypeScript types suggest entity fields
(`name`, `aliases`, `type`) exist at the top level. **They don't.** The
API wraps them in a `.report` container. The generated client does NOT
unwrap this automatically.
```typescript
// WRONG — name will be undefined:
const res = await client.getNamedEntityReport(neid);
const name = res.name; // undefined!
// CORRECT — always access through .report:
const res = await client.getNamedEntityReport(neid);
const name = res.report?.name ?? (res as any).name ?? neid;
const aliases = res.report?.aliases ?? (res as any).aliases ?? [];
const type = res.report?.type ?? (res as any).type;
```
The `(res as any).name` fallback handles the case where the client is
eventually fixed to unwrap the response.
> **WARNING -- `getPropertyValues()` takes JSON-stringified arrays**: The `eids`
> and `pids` parameters must be JSON-encoded strings, NOT native arrays. The
> TypeScript type is `string`, not `string[]`. Passing a raw array will silently
> return no data.
```typescript
const values = await client.getPropertyValues({
eids: JSON.stringify(['00416400910670863867']),
pids: JSON.stringify(['name', 'country', 'industry']),
});
```
### `getLinkedEntities` only supports graph node types
`getLinkedEntities(neid, { entity_type: ['document'] })` will fail at
runtime with _"entity_type document not a valid graph node type"_. The
`/entities/{neid}/linked` endpoint only supports three entity types:
**person**, **organization**, **location**. Documents, filings, articles,
financial instruments, events, and all other types are excluded — even
though the schema shows relationships like `filed` connecting organizations
to documents.
**Why?** The knowledge graph has two layers. The **graph layer** models
first-class entities (people, organizations, locations) as nodes with
edges between them — this is what `getLinkedEntities` traverses. The
**property layer** attaches everything else (documents, filings, financial
instruments, events) as property values on graph nodes. Documents aren't
"lesser" entities — they're stored differently because they're associated
with specific graph nodes rather than standing independently in the graph.
Understanding this distinction helps you generalize: if a target entity
type doesn't appear in the `getLinkedEntities` response, it's a property-
layer entity and you need `getPropertyValues` with the relationship PID.
To traverse relationships to non-graph-node types, use `getPropertyValues`
with the relationship PID instead. Relationship properties (`data_nindex`)
return linked entity IDs as values. Zero-pad the returned IDs to 20
characters to form valid NEIDs.
```typescript
const pidMap = await getPropertyPidMap(client);
const filedPid = pidMap.get('filed')!;
const res = await client.getPropertyValues({
eids: JSON.stringify([orgNeid]),
pids: JSON.stringify([filedPid]),
});
const docNeids = res.values.map((v) => String(v.value).padStart(20, '0'));
```
See the **cookbook** rule for a full "Get filings for a company" recipe.
### `getNEID()` vs `findEntities()` for entity search
- **`client.getNEID()`** -- simple single-entity lookup by name
(`GET /entities/lookup`). Best for resolving one company/person name.
- **`client.findEntities()`** -- expression-based search
(`POST /elemental/find`). Best for filtered searches (by type, property,
relationship). See `find.md` for the expression language.
## Common Entity Relationships
The knowledge graph connects entities through relationship properties
(`data_nindex` PIDs). These are the most common patterns:
| From | PID | To | How to traverse |
|---|---|---|---|
| Organization | `filed` | Document (filings) | `getPropertyValues` with the `filed` PID on the org's NEID |
| Organization | `employs` | Person | `getLinkedEntities` (person is a graph node type) |
| Person | `employed_by` | Organization | `getLinkedEntities` (organization is a graph node type) |
| Organization | `headquartered_in` | Location | `getLinkedEntities` (location is a graph node type) |
| Any entity | `related_to` | Any entity | `getLinkedEntities` for person/org/location; `getPropertyValues` for others |
**Key constraint:** `getLinkedEntities` only works for three target types:
**person**, **organization**, **location**. For documents, filings,
articles, financial instruments, and events, use `getPropertyValues` with
the relationship PID. See the cookbook rule (recipe #7) for a full filings
example.
**Traversal pattern for non-graph-node types:**
```typescript
// 1. Get the PID for the relationship
const schema = await client.getSchema();
const pids = schema.schema?.properties ?? [];
const filedPid = pids.find((p: any) => p.name === 'filed')?.pid;
// 2. Get linked entity IDs via getPropertyValues
const res = await client.getPropertyValues({
eids: JSON.stringify([orgNeid]),
pids: JSON.stringify([filedPid]),
});
// 3. Pad IDs to 20 chars to form valid NEIDs
const docNeids = res.values.map((v: any) => String(v.value).padStart(20, '0'));
// 4. Get details for each linked entity (response is nested under .report)
const reports = await Promise.all(
docNeids.map(async (neid: string) => {
const r = await client.getNamedEntityReport(neid);
return r.report ?? r;
}),
);
```
## Error Handling
```typescript
try {
const data = await client.getNEID({ entityName: '...' });
} catch (error) {
console.error('API Error:', error);
showError('Failed to load data. Please try again.');
}
```
Methods on `useElementalClient()` return data directly and throw on non-2xx
responses. For full `{ data, status, headers }` access, import the raw
functions instead:
```typescript
import { getArticle } from '@yottagraph-app/elemental-api/client';
const response = await getArticle(artid);
if (response.status === 404) { /* handle not found */ }
```
## Lovelace MCP Servers (Optional)
Four MCP servers **may** be configured in `.cursor/mcp.json` for interactive
data exploration. They are NOT required — the REST client
(`useElementalClient()`) and skill docs cover the same data and are the
primary path for building features.
**Before referencing MCP tools, check your available tool list.** If tools
like `elemental_get_schema` don't appear in your MCP server list, the
servers aren't connected — just use the REST client and skill docs instead.
Do not report this as a problem; it's a normal configuration state.
| Server | What it provides |
|---|---|
| `lovelace-elemental` | Knowledge Graph: entities, relationships, events, sentiment, schema discovery |
| `lovelace-stocks` | Stock/financial market data |
| `lovelace-wiki` | Wikipedia entity enrichment |
| `lovelace-polymarket` | Prediction market data |
### Setup
`.cursor/mcp.json` is auto-generated by `init-project.js`. If it's missing,
run `node init-project.js --local` to regenerate it. For provisioned projects,
the servers route through the Portal Gateway proxy (no credentials needed).
For local development without a gateway, the servers require an
`AUTH0_M2M_DEV_TOKEN` environment variable.
These servers are also accessible from the browser through the Portal
Gateway, so you can build MCP tool exploration UIs if needed.
### When MCP Servers Are Available
If the MCP tools appear in your tool list, you can use them for interactive
exploration alongside the REST client:
| Tool | Purpose |
|---|---|
| `elemental_get_schema` | Discover entity types (flavors), properties, and relationships |
| `elemental_get_entity` | Look up entity by name or NEID; returns properties |
| `elemental_get_related` | Related entities with type/relationship filters |
| `elemental_get_relationships` | Relationship types and counts between two entities |
| `elemental_graph_neighborhood` | Most influential neighbors of an entity |
| `elemental_graph_sentiment` | Sentiment analysis from news articles |
| `elemental_get_events` | Events for an entity or by search query |
| `elemental_health` | Health check |
MCP is convenient for schema discovery and entity resolution during planning.
For building UI features, always use the REST client (`useElementalClient()`).
Built on the Agent Skills standard
Aether
**Stack:** Nuxt 3 (SPA), Vue 3 Composition API (`<script setup>`), Vuetify 3, TypeScript (required), Auth0. **Structure:** `pages/` (file-based routing), `components/`, `composables/`, `server/api/`, `agents/` (Python ADK), `mcp-servers/` (Python FastMCP).
by AgentHub Crawler
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) and other AI assistants when working with code in this repository. **IMPORTANT**: This project uses [bd (beads)](https://github.com/steveyegge/beads) for ALL issue tracking. Use `bd` commands instead of markdown TODOs, task lists, or other tracking methods.
by AgentHub Crawler
Agent Handbook
Reference for coding agents working on the Pokemon Soullink Tracker.
by AgentHub Crawler
Sign in to leave a comment.
Molecule Project - Claude Code Configuration
Scala 3 library to compose domain-tailored data from SQL databases
by AgentHub Crawler