# Choose an API surface

> Match configuration, warehouse reads, and inbound event delivery to the correct Hodoflow API surface and prerequisites.

Hodoflow has related API surfaces, not one interchangeable API. Choose by the
job your integration performs. Authentication is shared; versioning, media
types, pagination, errors, and prerequisites are not.

## Decision table

| Goal | Surface | Mount | Prerequisite | Scope |
|---|---|---|---|---|
| List or manage workflows, dashboards, data models, integrations, accounts, and runs | JSON:API | `/api/json/v1` | Service account in the target workspace | `api_read` or `api_write` by method |
| Read latest or historical warehouse records | GraphQL | `/api/graphql` | Existing data model slug and warehouse records | `api_read` for `factQuery` |
| Send an inbound delivery into an active workflow | Webhook ingestion | `/api/v1/webhooks/:token` | Saved, active webhook workflow and selected service account | `webhooks` |
| Obtain a bearer token | Token exchange | `/api/v1/oauth/token` | Active service-account client credentials | No bearer scope; the resulting token carries configured scopes |

If you are still unsure, start with JSON:API. A bounded workflow list is
read-only and succeeds in an empty workspace, making it the deterministic way to
verify the host, credential, workspace, scope, and response headers.

## JSON:API for configuration and observability

JSON:API exposes reviewed resource operations under a path-versioned `v1`
mount. Collection requests can filter, sort, select sparse fields, include
relationships, and paginate where the individual operation declares support.
Always send `Accept: application/vnd.api+json`; writes also send that media type
as `Content-Type` and should carry an `Idempotency-Key`.

Do not query warehouse fact rows through JSON:API. Use GraphQL for that logical
table view. Begin with [List and inspect workflows](/guides/list-workflows) or
browse the [JSON:API reference](/reference/json-api/workflows).

## GraphQL for warehouse reads

GraphQL exposes `health` and one authenticated data operation, `factQuery`.
There are no public mutations or subscription fields. `factQuery` resolves a
data model by slug, returns one latest row per entity or a bounded timeline, and
uses offset pagination. Its default limit is 50, maximum is 250, and complexity
usually imposes a smaller useful page size.

A new workspace cannot complete this path until it has a data model and facts.
Follow [Run your first warehouse query](/graphql/first-query), which shows how to
obtain the slug through JSON:API before querying.

## Webhook ingestion for inbound events

Webhook ingestion is not an outbound event-subscription product. A sender posts
JSON to the opaque trigger path shown by an active webhook workflow. The request
must use the exact service account selected on that trigger, with the `webhooks`
scope. A stable delivery key prevents duplicate workflow runs during the
seven-day retention window.

The `202` response means the delivery was accepted and a run was enqueued; it
does not mean the workflow completed. Continue by retrieving the returned run
through JSON:API or opening Monitor. Start with [Send your first delivery](/webhook-ingestion/first-delivery).

## Do not blend the contracts

- JSON:API's `v1` path and `x-api-version: v1` do not select a GraphQL schema version.
- GraphQL field errors use a GraphQL `errors` array; JSON:API plug and resource errors use an `errors` document.
- JSON:API idempotency replay applies to unsafe JSON:API requests, not to read-only `factQuery`.
- Webhook delivery deduplication is a separate seven-day contract.
- Only GraphQL uses query complexity, nesting-depth, and token-count limits.

Next, configure [Base URLs, workspaces, and versions](/get-started/base-urls-workspaces-versions)
or complete the [first API request](/get-started/first-api-request).
