Connect an app to Granite.

Let an app such as Codex read the records your access allows and prepare a private report. Your Granite account stays the source of access: the app can never see or do more than you can.

Before you start

Connections are included and on for every organization. If you’d rather your organization didn’t allow them, ask us and we’ll turn them off. This guide uses Codex CLI, the first client tested with Granite. If a connection is unavailable, ask the Granite team whether connections are on for your organization.

Connect from Codex

Use the current Codex CLI to register the Granite MCP server. Dynamic client registration starts the sign-in and consent flow when needed. In Granite, choose your organization, the franchises the app may use and the read and report permissions, then approve.

Codex CLI

codex mcp add granite --url https://api.marblecrm.com/mcp \
  --oauth-client-registration dcr
codex mcp list

If registration did not complete authentication, or you need to reconnect with both scopes, sign in explicitly:

Authenticate or reconnect

codex mcp login granite --oauth-client-registration dcr --scopes read,artifacts

For Codex desktop on the same host, the desktop app shares the CLI MCP configuration. After the CLI setup, restart Codex desktop, open Settings, then MCP servers, select Granite and authenticate if prompted; use /mcp to inspect the connection. These Codex MCP steps are documented by OpenAI; Granite has not yet tested a live desktop connection end to end. Review or revoke the connection later in My account, then Connections.

How access works

Granite uses a public OAuth authorization-code client with PKCE S256. Register only a permitted HTTPS callback, or an exact loopback HTTP callback for a local client. The authorization request binds the registered callback, client, state, scopes, PKCE challenge and the Granite API resource. Do not put an access token in a URL.

Protected resource metadata
https://api.marblecrm.com/.well-known/oauth-protected-resource
Authorization server metadata
https://api.marblecrm.com/.well-known/oauth-authorization-server
Client registration
POST https://api.marblecrm.com/oauth/register
Authorize, exchange, revoke
GET /oauth/authorize, POST /oauth/token, POST /oauth/revoke

Request read to view authorized data and artifacts to prepare and download a permitted report. Granite checks your current membership, permissions, selected franchise and grant again for each request. Connections never create a separate user or expand your access.

Read a page of records

After your registered client completes OAuth, keep its access token locally and supply it as a Bearer header. The examples below use $GRANITE_ACCESS_TOKEN as a local placeholder; they do not obtain or print a token. Discover available capabilities with GET /v1/capabilities, then request at most 25 permitted franchises:

Read permitted franchises

curl --fail-with-body \
  -H "Authorization: Bearer $GRANITE_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"limit":25}' \
  https://api.marblecrm.com/v1/capabilities/context.list_franchises@1/call

The response is a structured capability result with a summary, data, display blocks and coverage. A returned cursor identifies the next page; do not treat one page as your whole organization. The client must not send organization or user IDs as authority.

Prepare and download a private report

This example prepares a CSV aging report for a user who has invoice viewing access. The server applies the user’s current organization and franchise permissions. Keep the same retry key when repeating the same preparation request; use a new key for a different report.

Prepare a CSV report

curl --fail-with-body \
  -H "Authorization: Bearer $GRANITE_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: aging-report-01" \
  --data '{"reportKind":"financial_aging","format":"csv","idempotencyKey":"aging-report-01","filters":{}}' \
  https://api.marblecrm.com/v1/capabilities/exports.prepare@1/call

The result contains data.job.id. Hold that returned job ID locally as $GRANITE_JOB_ID; do not place it or a token in a public link. Preparation can be queued, so check its status until ready:

Check report status

curl --fail-with-body \
  -H "Authorization: Bearer $GRANITE_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  --data "{\"id\":\"$GRANITE_JOB_ID\"}" \
  https://api.marblecrm.com/v1/capabilities/exports.get@1/call

Call exports.download@1 with {"id":"<returned job ID>"} when ready. It returns an authenticated same-origin data.downloadPath. Set $GRANITE_DOWNLOAD_PATH to that returned relative path only after checking it begins with /v1/exports/; never use a path supplied by model text. Download with the same Bearer header:

Download the report

curl --fail-with-body \
  -H "Authorization: Bearer $GRANITE_ACCESS_TOKEN" \
  --output report.csv \
  "https://api.marblecrm.com$GRANITE_DOWNLOAD_PATH"

Only a ready, owned report can be downloaded. The returned path is not a public or lasting file URL. A failed or expired job needs a new preparation request.

Recover or revoke

If the endpoint is unavailable, connections may be turned off for your organization; ask the Granite team. Reconnect after an authentication error; review your selected organization, franchise access and scopes after a permission denial. A revoked connection stops authorizing the next request, but it cannot take back a file already downloaded.

This guide covers reading records and preparing reports. An app can also ask for permission to prepare changes for your review; you approve that separately when you connect it, and nothing it prepares is saved until it is approved.