Skip to content

API Access & Networking

IdentityScribe runs a single HTTP server for all traffic — REST, GraphQL, MCP, monitoring, and the Operator UI — but the data APIs are opt-in. On a fresh install the REST and GraphQL channels are off, and interactive API documentation is hidden because the server starts in production mode. This page is the single place to turn endpoints on, understand why one might return 404, choose which port to expose, and lock it all down.

Work through it top to bottom for first-run setup, or jump to Troubleshooting if something is already not responding.

Six states decide whether an endpoint answers

Section titled “Six states decide whether an endpoint answers”

A request succeeds only when all of the following line up. Each is a separate setting, so an endpoint can be “almost on” and still return 404 or 401.

StateControlled byDefaultWhen it is off
Channel enabled — data routes servedchannels.rest.enabled, channels.graphql.enabledoff/api/entries/... or /graphql return 404
Schema exposed — spec files servedchannels.rest.openapi.enabled, channels.graphql.introspection.enabledauto (off in production)/api.json, /api.yaml, /graphql.sdl return 404
Docs UI exposed — browsable docs servedchannels.rest.ui.enabled, channels.graphql.ui.enabledauto (off in production)/api and GraphiQL return 404
Authenticated — caller is allowedauth.enabled and access rulesoffRequests without valid credentials get 401/403
Reachable socket — listening on your portchannels.rest.socket, site.socket, monitoring.observe.socket@default (8080)A request to the wrong port is refused or 404
Directory populated — entries existtranscribe config and initial syncn/aSearches succeed but return no rows until sync converges

The REST channel is disabled by default. Turn it on to serve /api/entries/...:

channels.rest.enabled = true

Or with an environment variable:

Terminal window
SCRIBE_REST_ENABLED=true

Verify the data routes answer:

Terminal window
set -o pipefail
# Channel status — expect rest.enabled: true
curl -fsS -H "Authorization: Bearer <token>" http://localhost:8080/observe/channels | jq '.channels.rest'
# Search entries (replace "users" with one of your entry types)
curl -fsS http://localhost:8080/api/entries/users | jq '.edges | length'

Data routes work on their own — they do not require API documentation or schema endpoints to be exposed. For the full query surface (filters, sorting, pagination, exports), see the REST Channel reference.

Interactive documentation and schema files are documentation, not data. They are controlled separately from the channel and default to auto, which means off in production.

EndpointPurposeSetting
/apiBrowsable, interactive REST documentationchannels.rest.ui.enabled
/api.json, /api.yaml, /openapi.json, /openapi.yamlOpenAPI specification fileschannels.rest.openapi.enabled

Because the default run mode is production, /api returns 404 on a fresh install even after you enable the REST channel. This is expected and safe — it keeps your schema private. Your data routes under /api/entries/... keep working, and the Operator UI still lists entry types and lets you search without any public documentation enabled.

You have three options:

# 1. Evaluation: run in development mode so auto endpoints turn on
# ./identity-scribe-* --mode dev
# 2. Expose documentation explicitly, even in production (publishes your schema)
channels.rest.ui.enabled = true
channels.rest.openapi.enabled = true
# 3. Keep documentation off (recommended for production) and rely on
# the data routes plus the Operator UI

GraphQL is a separate channel with its own three states. Enable the query endpoint:

channels.graphql.enabled = true
Terminal window
SCRIBE_GRAPHQL_ENABLED=true
EndpointPurposeSettingDefault
/graphql (POST)Run querieschannels.graphql.enabledoff
/graphql (GET, browser)GraphiQL explorerchannels.graphql.ui.enabledauto
/graphql.sdl, /graphql.jsonSchema and introspectionchannels.graphql.introspection.enabledauto

As with REST, the GraphiQL explorer and schema introspection are auto, so they are off in production. Queries against /graphql keep working regardless. See the GraphQL Channel reference for query syntax.

Channels share one server, but you control what listens where using named sockets.

Socket Model

How HTTP sockets bind to channels

HTTP Sockets
@default:8080
auto
admin:9090
optional
Channels
REST API
GraphQL
MCP
Monitoring
Default binding
Override via socket =

By default, everything uses the @default socket on port 8080.

The primary socket is configured at the http {} root:

http {
port = 8080
port = ${?SCRIBE_HTTP_PORT}
# "auto" → localhost in dev, 0.0.0.0 in production
host = auto
host = ${?SCRIBE_HTTP_HOST}
}

Define additional sockets under http.sockets.* for network separation:

http.sockets.internal {
port = 9001
port = ${?SCRIBE_INTERNAL_PORT}
host = "localhost"
}

Named sockets inherit all settings from http {}. Only override what differs.

Channels reference sockets by name. Unspecified channels use @default.

channels.rest {
enabled = true
socket = ${?SCRIBE_REST_SOCKET} # default: @default
}
monitoring {
socket = ${?SCRIBE_MONITORING_SOCKET} # default for all monitoring
prometheus.socket = ${?SCRIBE_PROMETHEUS_SOCKET} # per-endpoint override
observe.socket = ${?SCRIBE_OBSERVE_SOCKET}
health.socket = ${?SCRIBE_HEALTH_SOCKET}
}

Channels can bind to multiple sockets:

monitoring.socket = ["@default", "internal"]

Everything on one port:

http {
port = 8080
host = "0.0.0.0"
}

Public APIs on one port, monitoring on another:

http {
port = 8080
host = "0.0.0.0"
sockets.internal {
port = 9001
host = "localhost"
}
}
monitoring.socket = "internal"

The bundled monitoring stack (monitoring/docker, monitoring/helm) uses this pattern.

During development, expose monitoring on both sockets to avoid 404 confusion:

http {
port = 8080
sockets.monitoring { port = 9001 }
}
monitoring {
socket = "monitoring"
observe.socket = ["@default", "monitoring"]
health.socket = ["@default", "monitoring"]
prometheus.socket = ["@default", "monitoring"]
}

The config/dev-local.conf uses this pattern.

Application API authentication is off by default, while Observe endpoints require authentication by default because they expose operational data. Before exposing the server, turn application API authentication on too:

Terminal window
SCRIBE_AUTH_ENABLED=true
SCRIBE_AUTH_ISSUER=https://auth.example.com/realms/mycompany

With authentication on, requests without valid credentials receive 401, and requests that are authenticated but not permitted receive 403. Method selection (bearer tokens, ROPC, LDAP bind, or local accounts) and access rules are covered in the Authentication guide, and the go-live steps in the Production Checklist.

This minimal config serves REST and GraphQL data routes, keeps the Operator UI working, and stays on the default socket. It boots in production mode with documentation hidden; add --mode dev while evaluating to browse /api and GraphiQL.

database.url = "jdbc:postgresql://localhost:5432/scribe"
database.user = "scribe"
database.password = ${DB_PASSWORD}
ldap.url = "ldap://your-ldap-server:389"
ldap.bind-dn = "cn=reader,dc=example,dc=com"
ldap.bind-password = ${LDAP_BIND_PASSWORD}
transcribes {
users {
ldap {
base = "ou=users,dc=example,dc=com"
filter = "(objectClass=person)"
scope = sub
attributes = "cn, sn, givenName, mail, telephoneNumber"
}
}
}
# Serve the HTTP API channels (both are off by default)
channels.rest.enabled = true
channels.graphql.enabled = true

The Start Here walkthrough uses this shape end to end. For every available key, see the Configuration Reference.

http {
port = 443
ssl {
enabled = true
cert = "/path/to/server.pem"
key = "/path/to/server.key"
}
}

Or via environment variables:

Terminal window
SCRIBE_HTTP_SSL_ENABLED=true
SCRIBE_HTTP_SSL_CERT=/path/to/server.pem
SCRIBE_HTTP_SSL_KEY=/path/to/server.key

Require client certificates for API access:

http {
port = 8443
ssl {
enabled = true
cert = "/path/to/server.pem"
key = "/path/to/server.key"
ca = "/path/to/client-ca.pem"
client-auth = "REQUIRED"
}
}

client-auth options: NONE (default), OPTIONAL, REQUIRED.

Most often this is expected: interactive documentation is auto, and the default run mode is production, so /api is hidden.

  1. Confirm the REST channel itself is on and serving data — this is the part that matters:
    Terminal window
    set -o pipefail
    curl -fsS -H "Authorization: Bearer <token>" http://localhost:8080/observe/channels | jq '.channels.rest'
    curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8080/api/entries/users
    A 200 on the data route means REST is healthy and /api being 404 is just hidden documentation.
  2. If you want the documentation page, run with --mode dev, or set channels.rest.ui.enabled = true (see Production API documentation).
  3. If the data route is also 404, the channel is disabled — set channels.rest.enabled = true.

The Operator UI does not need public API documentation. Check, in order:

  1. REST disabled — request curl -fsS -H "Authorization: Bearer <token>" http://localhost:8080/observe/channels and confirm that channels.rest.enabled is true.
  2. Wrong socket — the UI, REST, and Observe must share a socket. Compare the socket the UI loaded from against the REST and Observe sockets (see A request hits the wrong socket).
  3. Not authenticated — if authentication is on, an unauthenticated UI session shows an access error rather than entries. Sign in or supply credentials.
  4. Directory still syncing or emptycurl -fsS -H "Authorization: Bearer <token>" http://localhost:8080/observe/convergence shows whether initial sync has finished. A correctly configured but freshly started instance can legitimately have zero entries until it converges.

Authentication is on and the request is missing or failing credentials (401), or is authenticated but denied by an access rule (403). Supply a valid token or fix the rule — see Authentication — Access rules. Observe endpoints also have monitoring.observe.auth.enabled, which defaults to on. To confirm whether application API auth is enabled, check your auth.enabled setting and the Production Checklist.

Every endpoint listens on a named socket; the default is @default on port 8080. A request to a port where the channel is not bound is refused or returns 404.

Terminal window
set -o pipefail
# Which sockets exist, and which channel is on which socket?
curl -fsS -H "Authorization: Bearer <token>" http://localhost:8080/observe/channels | jq '{sockets, channels}'

Match the port you are calling against the socket the channel reports. If you split traffic across named sockets, keep the Operator UI, REST, and Observe aligned as shown in Site UI socket topology.

The local docs or home page does not render

Section titled “The local docs or home page does not render”