Start here
Authentication
GraphQL accepts a Bearer token from an organization API OAuth client or from sign-in. MCP uses a separate mcp-scoped OAuth token.
Every GraphQL request requires an Authorization header:
Authorization: Bearer <token>
Requests without a valid token receive 401 Unauthorized and a WWW-Authenticate challenge that points at the GraphQL protected-resource metadata.
Organization API OAuth
External integrations should not collect Constructable passwords. Create a confidential API client in Organization Settings → API Access, then run authorization code with PKCE against the same authorization server MCP uses, requesting the api scope.
An organization admin creates the client (organization_settings.write). Constructable shows the client secret once. Store it in your integration. Revoke the client to invalidate tokens immediately.
The authorizing user must belong to the organization that owns the client. After consent, the access token acts as that user in that organization only. It cannot query another organization, even if the user has other memberships. Product permissions still apply. Membership is checked again on every GraphQL request.
| Well-known document | URL |
|---|---|
| Authorization server | https://api.constructable.ai/.well-known/oauth-authorization-server |
| Protected resource | https://api.constructable.ai/.well-known/oauth-protected-resource/graphql |
Supported values:
- Scope:
api - Response type:
code - Grants:
authorization_code,refresh_token - Code challenge:
S256(required) - Token endpoint auth:
client_secret_basicorclient_secret_post - Redirect URI: HTTPS, or HTTP on
localhost/127.0.0.1
Dynamic client registration (POST /oauth/register) is MCP-only. It cannot create an organization API client.
Authorize the user
https://api.constructable.ai/oauth/authorize
?client_id=<client_id>
&redirect_uri=<redirect_uri>
&response_type=code
&scope=api
&state=<opaque-state>
&code_challenge=<S256>
&code_challenge_method=S256
Constructable signs the user in (if needed) and shows Authorize API access. On confirm, the browser redirects to your redirect URI with code and state. Send the same redirect_uri on the token request that you registered on the client.
Exchange the code
POST https://api.constructable.ai/oauth/token with application/x-www-form-urlencoded:
grant_type=authorization_code
&code=<code>
&redirect_uri=<redirect_uri>
&client_id=<client_id>
&client_secret=<client_secret>
&code_verifier=<pkce-verifier>
A successful response looks like:
{
"access_token": "...",
"token_type": "Bearer",
"expires_in": 7200,
"refresh_token": "...",
"scope": "api"
}
Access tokens expire in two hours. Refresh with grant_type=refresh_token, the refresh token, client id, and client secret.
Call GraphQL
POST https://api.constructable.ai/graphql
Authorization: Bearer <access_token>
Content-Type: application/json
{
"query": "query { viewer { userId organizationId } }",
"variables": {}
}
viewer.organizationId is the organization that owns the API client. Pass that id to organization(id:).
MCP mcp tokens cannot call GraphQL. API api tokens cannot call MCP. API tokens also cannot create or revoke API clients, and they do not expose user-scoped mutations (user { ... }).
On local development, use the same paths on your Rails API origin, for example http://localhost:3000.
Session tokens
Session tokens come from POST /api/users/sign_in.
POST https://api.constructable.ai/api/users/sign_in
Content-Type: application/json
{
"email": "[email protected]",
"password": "your-password"
}
A successful response looks like:
{
"user_id": "019f...",
"requires_otp": false,
"sessions": [
{
"token": "eyJ...",
"membership_type": "User",
"organization_id": null,
"organization_name": null
},
{
"token": "eyJ...",
"membership_type": "OrganizationMembership",
"organization_id": "019e...",
"organization_name": "Northridge Construction"
}
]
}
Use a session whose membership_type is OrganizationMembership (or CompanyMembership when you are acting as a company user). That token sets the organization on the request. A User session authenticates you but does not select an organization.
If the account has OTP enabled and you omit code, the response is requires_otp: true with an empty sessions array. Retry the same request with code set to the one-time password.
Sign out with POST /api/users/sign_out using the same Bearer token. That destroys only the session you present.
You can send query, variables, and operationName as JSON. GraphQL errors are returned in the errors array with HTTP 422. Successful data responses use HTTP 200.
Which organization a token belongs to
viewer.organizationId is the organization bound to the token. GraphQL queries still take an explicit organization(id:) argument; pass that same id. If you pass a different organization id the token cannot access, organization resolves to null.
- API OAuth: the organization that owns the client.
- Organization or company session: the membership on that session. A user with several organization memberships receives one session per membership at sign-in. Keep the token that matches the organization you intend to query.
MCP OAuth
MCP clients should not collect passwords. They use OAuth 2.1 with PKCE on the same authorization server, with the mcp scope:
| Well-known document | URL |
|---|---|
| Authorization server | https://api.constructable.ai/.well-known/oauth-authorization-server |
| Protected resource | https://api.constructable.ai/.well-known/oauth-protected-resource |
| MCP-specific aliases | the same documents under /mcp |
Supported values:
- Scope:
mcp - Response type:
code - Grants:
authorization_code,refresh_token - Code challenge:
S256 - Token endpoint auth:
none(public clients) - Dynamic registration:
POST /oauth/register
The authorization endpoint is https://api.constructable.ai/oauth/authorize. The user signs in on Constructable, confirms the client, and the client receives an access token. Send that token as Authorization: Bearer to https://api.constructable.ai/mcp.
MCP tokens are not interchangeable with GraphQL tokens. Call GraphQL with an organization API OAuth access token or a session token. MCP tools can also execute GraphQL on your behalf.
See MCP for tools, permissions, and client setup.
Permissions
Authentication proves who you are. Authorization still applies on every field and mutation. Missing permission typically becomes a GraphQL error, not a silent empty list, for writes. Reads are scoped to records the user can see. See Permissions.