# Geo Query

> Query the Geo knowledge graph via GraphQL. Use when looking up entities, searching by type, exploring relations, discovering schemas, or inspecting entity properties. Triggers on "look up", "find entity", "query geo", "search the graph", "what type is", "show me relations", "get entity".

- **Type:** Skill
- **Install:** `agentstack add skill-geobrowser-geo-skills-geo-query`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [geobrowser](https://agentstack.voostack.com/s/geobrowser)
- **Installs:** 0
- **Category:** [Search](https://agentstack.voostack.com/c/search)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [geobrowser](https://github.com/geobrowser)
- **Source:** https://github.com/geobrowser/geo-skills/tree/main/geo-query

## Install

```sh
agentstack add skill-geobrowser-geo-skills-geo-query
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Geo Knowledge Graph — Querying

Query and explore entities, types, properties, and relations in the Geo knowledge graph via its GraphQL API.

## When to apply

Use this skill when the user wants to:

- Look up an entity by ID.
- Search for entities of a given type (optionally scoped to a space).
- Explore what properties and relations an entity has.
- Discover the schema for an unfamiliar entity type before publishing.
- Find type, property, or relation type IDs.

## API basics

- **Endpoint:** `https://testnet-api.geobrowser.io/graphql`
- **Method:** `POST` with `Content-Type: application/json`
- **Auth:** none required for reads.
- **UUIDs:** 32-char hex, no dashes (e.g. `4faff0b210cb49958e20109409b8699c`).
- **Browser links:** `https://www.geobrowser.io/space/{spaceId}/{entityId}`.

## Core concepts (compact)

- **Entity:** a unique node in the graph (person, place, article, etc.). Has an ID, `name`, `description`, `types`, `values`, and `relations`.
- **Property:** a typed attribute on an entity (`text`, `date`, `boolean`, `decimal`, `integer`, `float`, `url`).
- **Relation:** a typed edge between two entities. Relations are themselves entities — they can have their own properties.
- **Type:** a category (`Person`, `Article`, …). Types define a schema of default properties that every entity of that type inherits.
- **Space:** an independent community/topic scope. An entity can live in multiple spaces; each has its own perspective.

Full conceptual details: see `reference.md`.

## List query: `entities` vs `entitiesConnection`

There are two list queries with the same top-level args (`typeId`, `spaceId`, `typeIds`, `spaceIds`, `filter`, `first`, `offset`, `orderBy`) but different shapes. **Choose based on result set size**:

|              | `entities`                                        | `entitiesConnection`                        |
| ------------ | ------------------------------------------------- | ------------------------------------------- |
| Return shape | flat array                                        | `{ nodes, edges, pageInfo, totalCount }`    |
| Pagination   | `first` + `offset` **(capped at offset 1000)**    | `first` + cursor (`after`/`before`)         |
| Use when     | small, bounded lookups; default for > {
  const out: Array = [];
  let cursor: string | null = null;

  while (true) {
    const afterClause = cursor ? `after: "${cursor}"` : "";
    const res = await fetch("https://testnet-api.geobrowser.io/graphql", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        query: `{
        entitiesConnection(typeId: "${typeId}", spaceId: "${spaceId}", first: 500 ${afterClause}) {
          nodes { id name }
          pageInfo { hasNextPage endCursor }
        }
      }`,
      }),
    });
    const { data } = await res.json();
    const conn = data.entitiesConnection;
    out.push(...(conn?.nodes ?? []));
    if (!conn?.pageInfo?.hasNextPage) break;
    cursor = conn.pageInfo.endCursor;
  }
  return out;
}
```

## Filtering

The `filter` arg accepts `EntityFilter` for field-level conditions:

```graphql
{
  entities(typeId: "TYPE_ID", filter: { name: { startsWithInsensitive: "Bitcoin" } }, first: 20) {
    id
    name
  }
}
```

Common `EntityFilter` fields:

- `id` — `UUIDFilter` (uses `is` / `isNot` / `in`; NOT `equalTo`).
- `name`, `description`, `createdAt`, `updatedAt` — `StringFilter` (`startsWithInsensitive`, `includesInsensitive`, `equalTo`).
- `spaceIds`, `typeIds` — `UUIDListFilter` (`anyEqualTo`).
- `relations`, `backlinks` — `EntityToManyRelationFilter` (`some`, `none`, `every`).
- `values` — `EntityToManyValueFilter`.
- `and`, `or`, `not`.

**CRITICAL — scope relation filters by space.** Cross-space relation filters can return `INTERNAL_SERVER_ERROR` on the server. Always include `spaceId` inside relation filters:

```graphql
relations: { some: { spaceId: { is: "SPACE_ID" }, toEntity: { ... } } }
```

**Prefer `none` over `every` for exclusion.** `every` means "all items must match the full condition" and misbehaves when items have different field values. Use `none` for "there is no X where Y":

```graphql
# "entity has no name value"
values: { none: { propertyId: { is: NAME_PROP_ID }, text: { isNull: false } } }
```

If complex filters return 500s, reduce `first` from 500 → 100 → 50.

## Schema discovery workflow

When you need to publish or understand an entity type you haven't seen before, **inspect an existing entity of that type** to learn the schema. Do this before assuming any property/relation IDs.

1. Find entities of the type (search by `typeId`).
2. Pick one and fetch it fully (all values + relations).
3. Read the property names and relation types from the result.
4. Note the IDs — property IDs, relation type IDs, and `toEntity` IDs for classification values.

```graphql
{
  entities(typeId: "86db141cf7cb471194ed39088926adb8", first: 3) {
    id
    name
    types {
      id
      name
    }
    values(first: 50) {
      nodes {
        property {
          id
          name
        }
        text
        date
      }
    }
    relations(first: 50) {
      nodes {
        type {
          id
          name
        }
        toEntity {
          id
          name
        }
      }
    }
  }
}
```

See `examples/discover-schema.md` for an end-to-end walkthrough.

## Finding type and property IDs by name

`Type` and `Property` are themselves types — you can query all of them:

```graphql
# All type definitions
{
  entities(
    typeId: "e7d737c536764c609fa16aa64a8c90ad"
    filter: { name: { includesInsensitive: "article" } }
    first: 20
  ) {
    id
    name
  }
}

# All property definitions
{
  entities(
    typeId: "808a04ceb21c4d888ad12e240613e5ca"
    filter: { name: { includesInsensitive: "date" } }
    first: 20
  ) {
    id
    name
  }
}
```

For relation types, inspect an entity that uses them — the `type { id name }` field on a relation gives you the ID.

## Well-known IDs

Prefer the SDK's exported constants where possible:

```typescript
import { SystemIds, ContentIds } from "@geoprotocol/geo-sdk";

(SystemIds.PERSON_TYPE, SystemIds.COMPANY_TYPE, SystemIds.PROJECT_TYPE, SystemIds.EVENT_TYPE);
(ContentIds.ARTICLE_TYPE, ContentIds.TALK_TYPE, ContentIds.PODCAST_TYPE, ContentIds.TOPIC_TYPE);
```

Common raw IDs (for GraphQL queries):

| Name            | ID                                 |
| --------------- | ---------------------------------- |
| Type (meta)     | `e7d737c536764c609fa16aa64a8c90ad` |
| Property (meta) | `808a04ceb21c4d888ad12e240613e5ca` |
| Person          | `4faff0b210cb49958e20109409b8699c` |
| Article         | `a2a5ed0cacef46b1835de457956ce915` |
| Topic           | `5ef5a5860f274d8e8f6c59ae5b3e89e2` |

Well-known space IDs and additional type IDs live in `reference.md`.

## curl sanity check

```bash
curl -s --compressed 'https://testnet-api.geobrowser.io/graphql' \
  -H 'Content-Type: application/json' \
  -d '{"query":"{ entities(typeId: \"4faff0b210cb49958e20109409b8699c\", first: 5) { id name } }"}' | jq .
```

## Critical gotchas — quick reference

1. **Offset cap 1000** on `entities`/`relations`/`values` — use `*Connection` + cursor for larger sets.
2. **`entities` is flat**, not `{ nodes { ... } }`.
3. **`typeId`/`spaceId` are top-level args**, not inside `filter`.
4. **Scope relation filters by `spaceId`** to avoid `INTERNAL_SERVER_ERROR`.
5. **`UUIDFilter` uses `is` / `isNot` / `in`**, not `equalTo`. `UUIDListFilter` uses `anyEqualTo`.
6. **Prefer `none` over `every`** for exclusion logic.
7. **Values come back as typed fields** (`text`, `date`, `boolean`, …), not a single `value`.
8. **Relation `id` ≠ `entityId`** — `id` is the edge (for deletion); `entityId` is the relation-as-entity (for relation properties).

## More

- `reference.md` — full filter spec, all well-known IDs, advanced patterns.
- `examples/lookup-entity.md` — walk through a single-entity fetch and what the shape looks like.
- `examples/paginate-list.md` — cursor pagination example with totalCount.
- `examples/discover-schema.md` — the schema-discovery workflow in practice.

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [geobrowser](https://github.com/geobrowser)
- **Source:** [geobrowser/geo-skills](https://github.com/geobrowser/geo-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-geobrowser-geo-skills-geo-query
- Seller: https://agentstack.voostack.com/s/geobrowser
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
