Skip to main content
Credal | Documentation

Search documentation

Type to search this documentation.

On this pageOverview

Service Accounts

A service account is a dedicated, non-human identity for machine-to-machine access to the Credal API. Instead of sharing a static organization-wide API key, create a service account for each integration—such as an ingestion pipeline or internal tool—grant only the permissions it needs, and mint scoped, expiring, and individually revocable tokens.

Service accounts always act as themselves. Unlike legacy API keys, a service account token never impersonates a human user. Every API call runs with the service account's own permissions, and every action is attributed to the service account in your audit logs.

  1. An organization admin creates a service account with a name and an email address on your organization's domain, such as ingestion-bot@yourcompany.com.
  2. The admin grants the service account access like any other user, including collection permissions, agent deployments, and IdP group membership. The service account can only access what it has explicitly been granted.
  3. The admin mints one or more tokens for the service account. Each token has a name, a set of scopes, and an expiration date.
  4. Your integration calls the v0 API with the token in the Authorization header.

Organization admins manage service accounts from Service Accounts in the Credal web app.

Provide a display name and an email address. The email domain must match your organization's domain. If you want integration permissions to sync to the service account, use an address that corresponds to its identity in your IdP or source systems.

Select the service account, then provide:

Field Description
Name A label for the token, such as prod-ingestion.
Description Optional free-form notes.
Scopes The parts of the API the token may call.
Expiration date The date the token stops working. Defaults to one year.

The token is displayed exactly once at creation time and is never shown again. Copy it immediately and store it in a secrets manager. Tokens look like:

text
cred_sa_<your_token>

If a token is compromised or no longer needed, revoke it from the same page. Revocation takes effect immediately. Deleting a service account revokes all of its tokens.

Scopes control which API surfaces a token may call. They are layered on top of the service account's own permissions: a token can never read data its service account has not been granted, regardless of scope. A token with no scopes cannot call any scoped endpoint.

Scope Grants
agent:message Send messages to deployed agents and read their responses.
catalog:write Upload and update documents through the catalog API.
catalog:read Read documents and collections through the catalog API.

Grant only the scopes each integration needs.

Store the token in an environment variable instead of hard-coding it:

Bash
export CREDAL_SERVICE_ACCOUNT_TOKEN="cred_sa_<your_token>"

Pass the token as a Bearer token in the Authorization header of every request:

http
Authorization: Bearer cred_sa_<your_token>

This endpoint requires the agent:message scope. The agent must have API access enabled and be deployed to the service account, just as it would be for a human user. agentId is required, and userEmail must be omitted.

Bash
curl -X POST https://app.credal.ai/api/v0/copilots/sendMessage \
  -H "Authorization: Bearer $CREDAL_SERVICE_ACCOUNT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "3a1b8c2d-0e4f-4a6b-9c8d-7e5f3a1b8c2d",
    "message": "Summarize open support tickets from this week."
  }'
Python
import os

import requests

response = requests.post(
    "https://app.credal.ai/api/v0/copilots/sendMessage",
    headers={
        "Authorization": f"Bearer {os.environ['CREDAL_SERVICE_ACCOUNT_TOKEN']}"
    },
    json={
        "agentId": "3a1b8c2d-0e4f-4a6b-9c8d-7e5f3a1b8c2d",
        "message": "Summarize open support tickets from this week.",
    },
)
response.raise_for_status()

result = response.json()
print(result["sendChatResult"]["response"]["message"])
TypeScript
const response = await fetch("https://app.credal.ai/api/v0/copilots/sendMessage", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CREDAL_SERVICE_ACCOUNT_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    agentId: "3a1b8c2d-0e4f-4a6b-9c8d-7e5f3a1b8c2d",
    message: "Summarize open support tickets from this week.",
  }),
});

if (!response.ok) {
  throw new Error(`Credal API request failed: ${response.status}`);
}

const result = await response.json();
console.log(result.sendChatResult.response.message);

A successful response looks like:

JSON
{
  "sendChatResult": {
    "type": "ai_response_result",
    "conversationId": "9f2e7d61-4c3b-4a5e-8f1d-2b6c9a0e7d61",
    "response": {
      "message": "Here is a summary of this week's open support tickets: ..."
    }
  }
}

To continue a conversation, pass the conversationId from a previous response in your next request:

JSON
{
  "agentId": "3a1b8c2d-0e4f-4a6b-9c8d-7e5f3a1b8c2d",
  "conversationId": "9f2e7d61-4c3b-4a5e-8f1d-2b6c9a0e7d61",
  "message": "Which of those tickets are highest priority?"
}

This endpoint requires the catalog:write scope. Send the file as multipart/form-data. documentExternalId is required and identifies the document in your system; uploading again with the same ID updates the document. Omit uploadAsUserEmail because the document is uploaded as the service account.

Bash
curl -X POST https://app.credal.ai/api/v0/catalog/uploadFile \
  -H "Authorization: Bearer $CREDAL_SERVICE_ACCOUNT_TOKEN" \
  -F "file=@quarterly-report.pdf" \
  -F "documentExternalId=quarterly-report-2026-q2" \
  -F "documentName=Quarterly Report Q2 2026" \
  -F "collectionId=5b2c9d3e-1f4a-4b6c-8d9e-0a7f5b2c9d3e"
Python
import os

import requests

with open("quarterly-report.pdf", "rb") as report:
    response = requests.post(
        "https://app.credal.ai/api/v0/catalog/uploadFile",
        headers={
            "Authorization": f"Bearer {os.environ['CREDAL_SERVICE_ACCOUNT_TOKEN']}"
        },
        files={"file": report},
        data={
            "documentExternalId": "quarterly-report-2026-q2",
            "documentName": "Quarterly Report Q2 2026",
            "collectionId": "5b2c9d3e-1f4a-4b6c-8d9e-0a7f5b2c9d3e",
        },
    )
response.raise_for_status()

print(response.json())  # {"documentId": "..."}
TypeScript
import { openAsBlob } from "node:fs";

const form = new FormData();
form.append("file", await openAsBlob("quarterly-report.pdf"));
form.append("documentExternalId", "quarterly-report-2026-q2");
form.append("documentName", "Quarterly Report Q2 2026");
form.append("collectionId", "5b2c9d3e-1f4a-4b6c-8d9e-0a7f5b2c9d3e");

const response = await fetch(
  "https://app.credal.ai/api/v0/catalog/uploadFile",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.CREDAL_SERVICE_ACCOUNT_TOKEN}`,
    },
    body: form,
  },
);

if (!response.ok) {
  throw new Error(`Credal API request failed: ${response.status}`);
}

console.log(await response.json()); // {"documentId": "..."}

A successful response returns the Credal document ID:

JSON
{
  "documentId": "7c4d1e8f-2a5b-4c7d-9e0f-3b8a7c4d1e8f"
}
Status Meaning
401 The token is missing, invalid, expired, or revoked.
403 Service accounts are not enabled for your organization, or the token lacks the scope required by the endpoint.
400 The request is invalid—for example, it contains userEmail or uploadAsUserEmail, or omits agentId when messaging an agent.

Error responses have this shape:

JSON
{
  "error": {
    "message": "This service account token is missing the required 'agent:message' scope."
  }
}
  • Use one service account per integration. This keeps permissions minimal and audit trails clear.
  • Grant the fewest scopes that work. A token for an ingestion job needs catalog:write, not agent:message.
  • Set short expirations and rotate tokens. Mint a new token before the old one expires, deploy it, then revoke the old token.
  • Store tokens in a secrets manager. The plaintext token is shown only once and cannot be recovered; it can only be revoked and reissued.
  • Never send impersonation fields. Service accounts act as themselves, so userEmail and uploadAsUserEmail are rejected.
Suggest an edit

Propose a replacement for this page. The site team reviews it before applying any changes.

Export
Documentation menu