API

REST API

Use the REST API to manage Inth organizations, members, projects, and API keys, run Code Audit scans, and work the Inbox from a server, script, or CI job.

To connect Codex or another MCP client with your Inth account, use Inth MCP instead.

Authentication

Every request carries a bearer token. There are two kinds.

Organization API keys are service credentials for CI jobs, servers, and integrations. They belong to an organization, not a person, and keep working when the person who created them leaves.

curl https://api.inth.com/v1/projects \
  -H "Authorization: Bearer inth_..."

Create a key in Organization settings > API keys. Inth shows the inth_... secret once. Store it like a deployment credential and never expose it in client-side code. Each key has access to its organization.

User tokens belong to a person. They are issued through the OAuth device authorization grant: a client requests a code, you approve it in the dashboard, and the client receives an access token plus a refresh token bound to your account. Requests act with your own organization memberships and roles, and stop working when you leave an organization. The endpoints are advertised in the authorization server metadata at https://api.inth.com/.well-known/oauth-authorization-server; use the device_authorization_endpoint and token_endpoint it lists with the inth-cli client. The Inth CLI, which wraps this flow, is not released yet.

Some operations act on behalf of a person rather than an organization and accept only a user token: creating an organization, inviting or changing members, creating, rolling, or deleting API keys, starting or unlocking Code Audit scans, and changing Inbox findings. A scan spends credits and a status change names who made it, so both are attributed to a person. An organization API key belongs to one organization and cannot found another, so POST /v1/organizations answers 403 to a key.

curl -X POST https://api.inth.com/v1/organizations \
  -H "Authorization: Bearer <user token>" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Acme", "slug": "acme" }'

The request answers 201 with the new organization's id, slug, name, and your owner role, and sets up billing on the free plan. Slugs are 3 to 20 characters of lowercase letters and numbers, with single hyphens between groups. A taken slug answers 409 with CONFLICT.

See rate limits for the request limits and API key caps that apply to each kind of token.

Scopes

Each endpoint requires a scope. A token carries the scopes it was issued with, and a request still needs the membership role behind the token to allow the action.

ScopeGrants
organizations.readList the organizations you belong to.
organizations.writeCreate organizations you will own.
projects.readRead projects and their consent configuration.
projects.writeCreate, update, and delete projects and their consent configuration.
members.readList organization members and pending invitations.
members.writeInvite people, cancel invitations, change roles, and remove members.
api-keys.readList organization API keys.
api-keys.writeCreate, roll, and delete organization API keys.
code-audit.readRead connected repositories, scans, scan requests, and findings.
code-audit.writeStart scans and spend credits to unlock reports.
inbox.readRead Inbox findings and evidence.
inbox.writeUpdate finding status and create GitHub issues.
billing.readRead the plan, credit balance, and top-up settings.

A user token carries the scopes you approved at sign-in, and GET /v1/me lists them in scopes. Organization API keys hold a fixed set: organizations.read, projects.read, projects.write, api-keys.read, inbox.read, and billing.read. A key cannot create organizations, manage members, create keys, start or unlock scans, or change Inbox findings. Code Audit scopes are excluded from organization API keys, so these routes return 403 INSUFFICIENT_SCOPE before invoking Dashboard sync.

A request whose token lacks the scope answers 403 with the error code INSUFFICIENT_SCOPE and names the missing scope in a header:

WWW-Authenticate: Bearer error="insufficient_scope", scope="projects.write"

Resources

Every resource is top-level and every ID is global. A resource carries its own id, name, and slug where it has one; references to other resources are prefixed, such as organizationId on a project.

The organization is a query parameter, not part of the path or the body. Lists and creates of organization-owned resources take an optional organizationId. Listing or creating top-level organizations does not use it:

curl "https://api.inth.com/v1/members?organizationId=org_123" \
  -H "Authorization: Bearer <user token>"

Without it an OAuth token uses the account's saved Dashboard organization. If that choice is unavailable and the account has multiple organizations, supply organizationId explicitly to avoid acting in the wrong organization. A user token with one organization uses that organization, and an organization API key always acts in its own. A resource fetched by ID, such as a member, scan, or Inbox item, resolves its own organization; one outside your memberships answers 404, the same as an unknown ID. Consent settings live on the project as a consent object: read them with GET /v1/projects/{id} and change them with the same PATCH that renames the project.

{
	"id": "prj_123",
	"slug": "website",
	"name": "Website",
	"description": null,
	"organizationId": "org_123",
	"organizationSlug": "acme",
	"dashboardUrl": "https://inth.com/dashboard/acme/website",
	"consent": {
		"trustedOrigins": ["example.com", "*.example.com"],
		"branding": "inth",
		"backendUrl": "https://website-acme.inth.app/",
		"dashboardUrl": "https://inth.com/dashboard/acme/website/consent/overview",
		"version": "v2"
	}
}

Code Audit and the Inbox

A scan runs on the production branch of a repository the Inth GitHub App is connected to. The first scan of a repository is a free preview: the report shows a subset of the findings and reports how many are locked and what unlocking costs in credits. Starting a scan answers 202 with the scan once the workflow has created it, or with a scan request to poll when that takes longer than a few seconds.

curl -X POST https://api.inth.com/v1/code-audit/scans \
  -H "Authorization: Bearer <user token>" \
  -H "Content-Type: application/json" \
  -d '{ "repositoryId": "repo_123" }'

The repository decides the organization. Read the findings with GET /v1/code-audit/scans/{scanId}/issues. A free preview answers the visible findings plus lockedCount and unlockCredits; POST /v1/code-audit/scans/{scanId}/unlock spends the credits, and answers 402 with INSUFFICIENT_CREDITS when the balance is short. Check the balance first with GET /v1/billing.

Every scan also files its findings into the Inbox. Read them with GET /v1/inbox, and set a status with PATCH /v1/inbox/{itemId}, sending back the version you read so two people cannot overwrite each other.

Responses

Every endpoint wraps its result in a stable envelope:

{ "success": true, "data": {} }

List endpoints return data as an array plus a pagination object. Pass pagination.nextCursor as the cursor query parameter to fetch the next page. Cursors are opaque and specific to the endpoint and filters that returned them. Do not decode them or reuse them with another endpoint or filter set.

For example:

{
	"success": true,
	"data": [{ "id": "prj_123" }],
	"pagination": { "nextCursor": "cHJqXzEyMw", "hasMore": true }
}

Errors use the same envelope with success: false and a machine-readable error.code. Validation errors may include an error.details array.

{
	"success": false,
	"error": {
		"code": "NOT_FOUND",
		"message": "Project not found"
	}
}

Every response carries an X-Request-Id header. Include it when reporting an issue.

Stability

Changes within v1 are additive. Clients should ignore fields they do not recognize. Breaking changes use a new version prefix.

Treat resource IDs as opaque strings.

Endpoints

System

API keys

Billing

Code Audit

Inbox

Members

Organizations

Projects