Skip to Content
Elido is in closed beta — APIs are stable but rate-limits and quotas may change before GA. Request access →
GuidesGraphQL API

GraphQL API

Elido ships a GraphQL endpoint alongside the REST API. Every GraphQL field is implemented as a thin pass-through to the same api-core endpoints the REST surface uses, so anything you can do over GraphQL you can also do over REST — pick whichever fits the shape of your screen.

The endpoint is POST /graphql, hosted on the graphql-gateway service. Authentication is identical to REST: send your API token or OAuth2 access token in the Authorization: Bearer <token> header.

This is the foundation drop. Subscriptions (real-time clicks), persisted queries, federation, and response caching are all on the roadmap — see ADR-0031  for the full deferred-work list.

When to use GraphQL vs REST

  • Use GraphQL when a screen needs 4+ resources composed together (workspace + links + recent clicks + top-N) — collapse the round-trips into one query shaped to the screen.
  • Use GraphQL for mobile screens where bytes and round-trips matter on flaky networks.
  • Use REST + the SDK for everything else. Webhooks, bulk ingestion, file uploads, and admin tooling are all REST-first and will stay that way.

Authentication

curl -X POST https://api.elido.app/graphql \ -H "Authorization: Bearer $ELIDO_TOKEN" \ -H "Content-Type: application/json" \ -d '{"query": "{ me { id email } }"}'

ek_… API tokens (Personal access tokens, workspace machine tokens) and OAuth2 access tokens both work. Public introspection queries like { __schema { types { name } } } work without a token in development; production deploys gate introspection behind GRAPHQL_INTROSPECTION=true.

Examples

1. Dashboard landing — current user + workspace list

query DashboardLanding { me { id email fullName timezone } workspaces { id name role planTier } }

One round-trip, two REST endpoints folded into one response. The mobile app uses this as the first authenticated query after login.

query WorkspaceLinks($workspaceId: ID!, $after: String) { links(workspaceId: $workspaceId, first: 50, after: $after) { edges { node { id slug destinationUrl title status tags createdAt } } pageInfo { endCursor hasNextPage } } }

Pass pageInfo.endCursor from the previous response back as the next after to fetch the next page. hasNextPage is false when you’ve reached the end. Cursors are opaque — don’t decode them.

query Overview( $workspaceId: ID! $from: DateTime! $to: DateTime! ) { timeseries( workspaceId: $workspaceId from: $from to: $to interval: "day" ) { ts count } topLinks( workspaceId: $workspaceId from: $from to: $to limit: 10 ) { slug clicks } }

from/to are ISO-8601 timestamps. interval accepts "hour" or "day"; api-core validates and returns an error on anything else. The dashboard’s overview screen issues this exact query — GraphQL collapses what used to be two REST calls into one.

mutation CreateLink($input: CreateLinkInput!) { createLink(input: $input) { id slug destinationUrl createdAt } }

Variables:

{ "input": { "workspaceId": "1", "destinationUrl": "https://example.com/launch", "tags": ["marketing", "q4"] } }

updateLink(workspaceId, linkId, input) and deleteLink(workspaceId, linkId) round out the link mutations. Mirroring REST, mutations are idempotent on the server side — the gateway forwards the SDK’s auto-generated Idempotency-Key header.

What’s not in this drop

These are intentional omissions for the foundation phase. Each will land as its own update with a migration note:

  • Subscriptions — real-time click events. WebSocket transport, bridges to Redis where analytics-api already publishes clicks:link:<id>.
  • Persisted queries — production builds will ship a manifest hashed against trusted clients only. Today, ad-hoc queries are fine.
  • Federation — analytics and billing are good candidates to own their own subgraphs eventually.

Limits and conventions

  • Same per-token rate limits as REST (advertised via the X-RateLimit-* headers on the GraphQL response).
  • Errors follow GraphQL’s spec — top-level errors array; partial data may still be present in data.
  • IDs are stringified ("1", not 1) to keep BigInt-safe IDs consistent across the schema.