Credentials & OAuth
Connector instances need credentials before they can communicate with external services. Chainabit supports multiple credential types and handles OAuth token exchange and refresh automatically.
Credentials are encrypted at rest and never returned in API responses. Use the status endpoint to check whether credentials are stored.
Endpoints
| Method | Path | Description | Auth |
|---|---|---|---|
| POST | /connectors/instances/:id/credentials | Store credentials | JWT |
| GET | /connectors/instances/:id/credentials/status | Get credential status | JWT |
| DELETE | /connectors/instances/:id/credentials | Delete credentials | JWT |
| POST | /connectors/instances/:id/oauth/initiate | Initiate OAuth flow | JWT |
| GET | /connectors/oauth/:connectorKey/callback | OAuth callback (automated) | — |
Credential Types
credType | Use case | Required fields in credentials |
|---|---|---|
oauth2 | OAuth 2.0 tokens (managed automatically) | access_token, optionally refresh_token |
api_key | API key authentication | apiKey |
basic_auth | Username + password | username, password |
bearer_token | Static bearer token | token |
connection_string | Database connection string | connectionString |
custom | Any other credential shape | Any key-value pairs |
POST /connectors/instances/:id/credentials
Store or replace authentication credentials for a connector instance. Credentials are encrypted before storage and are not retrievable in plain text.
After storing credentials, test the connection to confirm they are valid.
Request
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Instance ID |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
credType | string | Yes | Credential type: oauth2, api_key, basic_auth, bearer_token, connection_string, or custom |
credentials | object | Yes | Key-value credential pairs (see Credential Types above) |
scopes | string[] | No | OAuth scopes granted (if applicable) |
Response
Response Example
{
"data": {
"id": "cred_xyz456",
"instanceId": "inst_abc123",
"credType": "api_key",
"rotatedAt": "2026-03-23T10:00:00Z"
}
}Code Examples
curl -X POST https://api.chainabit.com/api/v1/connectors/instances/$INSTANCE_ID/credentials \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"credType": "api_key",
"credentials": { "apiKey": "sk-your-api-key" }
}'curl -X POST https://api.chainabit.com/api/v1/connectors/instances/$INSTANCE_ID/credentials \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"credType": "connection_string",
"credentials": { "connectionString": "postgresql://user:pass@host:5432/db" }
}'curl -X POST https://api.chainabit.com/api/v1/connectors/instances/$INSTANCE_ID/credentials \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"credType": "bearer_token",
"credentials": { "token": "eyJ..." }
}'const BASE_URL = process.env.BASE_URL;
const TOKEN = process.env.TOKEN;
const INSTANCE_ID = process.env.INSTANCE_ID;
const response = await fetch(
`${BASE_URL}/connectors/instances/${INSTANCE_ID}/credentials`,
{
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
credType: "api_key",
credentials: { apiKey: "sk-your-api-key" },
}),
},
);
const { data } = await response.json();import requests, os
BASE_URL = os.environ["BASE_URL"]
TOKEN = os.environ["TOKEN"]
INSTANCE_ID = os.environ["INSTANCE_ID"]
response = requests.post(
f"{BASE_URL}/connectors/instances/{INSTANCE_ID}/credentials",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"credType": "api_key",
"credentials": {"apiKey": "sk-your-api-key"},
},
)
data = response.json()["data"]GET /connectors/instances/:id/credentials/status
Check whether credentials are stored for an instance and inspect metadata such as expiry and scopes. The actual credential values are never returned.
Request
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Instance ID |
Response
Response Example
{
"data": {
"configured": true,
"credType": "oauth2",
"expiresAt": "2026-04-23T10:00:00Z",
"scopes": ["channels:read", "chat:write"]
}
}Response Fields
| Field | Type | Description |
|---|---|---|
configured | boolean | true if credentials are stored |
credType | string | null | The stored credential type |
expiresAt | string | null | ISO 8601 expiry timestamp for OAuth tokens; null for non-expiring credentials |
scopes | string[] | null | Granted OAuth scopes; null for non-OAuth credentials |
Code Examples
curl https://api.chainabit.com/api/v1/connectors/instances/$INSTANCE_ID/credentials/status \
-H "Authorization: Bearer $TOKEN"const response = await fetch(
`${BASE_URL}/connectors/instances/${INSTANCE_ID}/credentials/status`,
{
headers: { Authorization: `Bearer ${TOKEN}` },
},
);
const { data } = await response.json();response = requests.get(
f"{BASE_URL}/connectors/instances/{INSTANCE_ID}/credentials/status",
headers={"Authorization": f"Bearer {TOKEN}"},
)
data = response.json()["data"]DELETE /connectors/instances/:id/credentials
Remove stored credentials for a connector instance. After deletion, the instance status changes to pending_auth.
Request
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Instance ID |
Response
Response Example
{
"data": {
"id": "cred_xyz456",
"instanceId": "inst_abc123",
"credType": "oauth2",
"rotatedAt": "2026-03-23T10:00:00Z"
}
}Code Examples
curl -X DELETE https://api.chainabit.com/api/v1/connectors/instances/$INSTANCE_ID/credentials \
-H "Authorization: Bearer $TOKEN"const response = await fetch(
`${BASE_URL}/connectors/instances/${INSTANCE_ID}/credentials`,
{
method: "DELETE",
headers: { Authorization: `Bearer ${TOKEN}` },
},
);
const { data } = await response.json();response = requests.delete(
f"{BASE_URL}/connectors/instances/{INSTANCE_ID}/credentials",
headers={"Authorization": f"Bearer {TOKEN}"},
)
data = response.json()["data"]POST /connectors/instances/:id/oauth/initiate
Start an OAuth 2.0 authorization flow for a connector instance. Returns a redirect URL to send the user to on the external service.
After the user approves access, the external service redirects back to Chainabit's callback URL. The callback endpoint is public, but it is authorized by a single-use state value with a short TTL. Chainabit exchanges the authorization code for tokens, stores them encrypted, and marks the instance as active.
Request
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Instance ID |
Response
Response Example
{
"data": {
"redirectUrl": "https://slack.com/oauth/v2/authorize?client_id=...&scope=...&state=abc123",
"state": "abc123"
}
}Response Fields
| Field | Type | Description |
|---|---|---|
redirectUrl | string | The authorization URL to open in a browser |
state | string | CSRF state token (used internally to validate the callback) |
Open
redirectUrlin the user's browser. Do not call the callback endpoint directly - it is invoked automatically by the external service.
Code Examples
curl -X POST https://api.chainabit.com/api/v1/connectors/instances/$INSTANCE_ID/oauth/initiate \
-H "Authorization: Bearer $TOKEN"const response = await fetch(
`${BASE_URL}/connectors/instances/${INSTANCE_ID}/oauth/initiate`,
{
method: "POST",
headers: { Authorization: `Bearer ${TOKEN}` },
},
);
const { data } = await response.json();
// Redirect the user to data.redirectUrl
window.location.href = data.redirectUrl;response = requests.post(
f"{BASE_URL}/connectors/instances/{INSTANCE_ID}/oauth/initiate",
headers={"Authorization": f"Bearer {TOKEN}"},
)
data = response.json()["data"]
print("Open this URL in your browser:", data["redirectUrl"])GET /connectors/oauth/:connectorKey/callback
This endpoint is called automatically by the external OAuth service after the user approves access. You do not call this endpoint directly.
Request
GET /connectors/oauth/:connectorKey/callback?code=...&state=...| Param | Location | Description |
|---|---|---|
connectorKey | Path | The connector being authorized, e.g. slack, notion |
code | Query | Authorization code issued by the external OAuth provider |
state | Query | Short-lived, single-use flow token generated by POST /connectors/instances/:id/oauth/initiate |
Response
Chainabit validates the state parameter, enforces the single-use flow record, exchanges the authorization code for access and refresh tokens, stores them encrypted, and responds with a 302 redirect back to the web app's connector settings page (success or error state reflected in the redirect query string).
Security note: the callback endpoint is intentionally public. Do not add a JWT guard to it, because OAuth providers must be able to reach it. The protection mechanism is the
statetoken plus the server-side flow state record.
Code Examples
Not applicable — this endpoint is invoked by the OAuth provider's redirect, not called directly by API clients.
Token Refresh
OAuth access tokens are refreshed automatically in the background. Chainabit runs a scheduled job every 5 minutes to detect tokens that are about to expire and refresh them using the stored refresh token.
If the refresh fails (e.g., the user revoked access), the instance status changes to error. Test the connection or re-initiate the OAuth flow to recover.