Skip to main content

Service Accounts

Service accounts are userless machine identities that authenticate to the Third Loop API. Unlike a user account, a service account has no person behind it — no email, no password, no interactive login. It exists so that automated systems (backend services, scheduled jobs, CI pipelines, internal integrations) can call the API on their own behalf.

Use a service account when the caller is a system. Use an API key when the caller is a person acting through a tool.

Overview

Service AccountAPI Key
Belongs toThe organizationAn individual user
IdentityMachine (no user)Acts as the issuing user
CredentialClient ID + client secretKey ID + secret key
Auth modelExchange secret for a short-lived access tokenSend the key on every request
Managed byAdministratorsThe user who owns the key
Best forBackend services, cron jobs, integrationsCoding tools, personal scripts

Each service account has three things that control what it can do:

  • Scope — a coarse permission level: read, write, or admin.
  • Roles — optional named role assignments that grant finer-grained policy-based permissions, the same roles used for users.
  • Token lifetime — how long an issued access token stays valid before the account must authenticate again.

Service accounts are scoped to a single organization. A service account can only see and act on resources within the organization that created it.

Adding a Service Account

Service accounts can be created by Administrators or any user with a role allowing the CreateServiceAccount API action. Creation returns the credentials the machine will use from then on.

Required and optional fields

FieldRequiredDescription
nameYesA descriptive label, e.g. nightly-ingest-job.
scopeYesOne of read, write, or admin.
clientIdNoA custom client identifier. If omitted, one is generated.
accessTokenExpirationNoAccess token lifetime as a number. Defaults to 60.
accessTokenExpirationUnitNoUnit for the lifetime — seconds, minutes, hours, days, etc. Defaults to minutes.

Creating via the API

curl -X POST https://api.thirdloop.ai/api/user/service-accounts \
-H "Content-Type: application/json" \
-d '{
"name": "nightly-ingest-job",
"scope": "read",
"accessTokenExpiration": 30,
"accessTokenExpirationUnit": "minutes"
}'

The response includes the service account id, its clientId, and the clientSecret:

{
"id": "3f2a1b8c-...",
"organizationId": "9c7e...",
"name": "nightly-ingest-job",
"scope": "read",
"clientId": "...",
"clientSecret": "...",
"accessTokenExpiration": 30,
"accessTokenExpirationUnit": "minutes",
"roles": []
}

Store the id and clientSecret in your secret manager — these are the two values needed to authenticate.

note

You must store the client secret when you first create a service account. It will not be shown to you again.

warning

Treat the client secret like a password. It grants full access at the account's scope and does not expire on its own. Never commit it to source control or embed it in client-side code.

Choosing a scope

Service account scopes are purely for quick identificaiton and initials role provisioning. You are free to modify service account roles and permissions after creation.

  • read — Retrieve data only. Correct for reporting, dashboards, and export jobs.
  • write — Create and modify resources. Correct for ingestion pipelines and integrations that push data in.
  • admin — Full administrative access, including managing other service accounts. Reserve for provisioning and platform automation.

Managing Service Accounts

Listing and inspecting

GET /api/user/service-accounts returns every service account in your organization. GET /api/user/service-accounts/{id} returns a single one.

Each record reports operational metadata useful for audit:

  • createdBy / createdAt — Who provisioned the account and when.
  • lastAccessed — The last time the account successfully authenticated. A null or stale value is a good signal that the account is unused and can be removed.
  • revokedBy — Who revoked the account, if it has been.
  • roles — Role assignments currently attached.

Updating

PUT /api/user/service-accounts/{id} changes the name or scope:

curl -X PUT https://api.thirdloop.ai/api/user/service-accounts/{id} \
-H "Content-Type: application/json" \
-d '{ "scope": "write" }'

A scope change takes effect on the next access token the account requests. Tokens already issued keep their original scope until they expire, so a downgrade is not fully in force until the current token lifetime has elapsed.

Assigning roles

Roles layer policy-based permissions on top of the account's scope. They use the same role definitions as user accounts — list them with GET /api/user/roles.

# Grant a role
curl -X POST https://api.thirdloop.ai/api/user/service-accounts/{id}/roles/{role_id}

# Revoke a role
curl -X DELETE https://api.thirdloop.ai/api/user/service-accounts/{id}/roles/{role_id}

Rotating the client secret

Rotation issues a new client secret and invalidates the old one. There are two paths, depending on who is rotating:

Administrator rotation — an admin rotates the secret for an account by ID:

curl -X POST https://api.thirdloop.ai/api/user/service-accounts/{id}/secret/rotate

Administrator rotation can be used either when an old client secret is lost or manually for invalidation purposes.

Self-service rotation — the service account rotates its own secret by presenting its current one:

curl -X POST https://api.thirdloop.ai/api/user/service-accounts/secret \
-H "Content-Type: application/json" \
-d '{ "id": "3f2a1b8c-...", "clientSecret": "<current_secret>" }'

Self-service rotation is intended to be used for long running processes or servers that should maintain their own secret rotations.

important

Rotation invalidates the previous secret immediately. Deploy the new secret to every consumer of the account before rotating, or the integration will start failing authentication.

Deleting

DELETE /api/user/service-accounts/{id} removes the service account. Any system still using its credentials will fail to authenticate on its next token request.

Before deleting, check lastAccessed to confirm the account is genuinely unused. If you are not certain, prefer rotating the secret and observing what breaks — that is reversible in a way deletion is not.

Authenticating a Service Account

Service accounts use a two-step token exchange: trade the long-lived client secret for a short-lived access token, then send that token on API requests. The client secret itself is never sent to regular API endpoints.

Step 1 — Exchange the secret for an access token

curl -X POST https://api.thirdloop.ai/api/user/service-accounts/login \
-H "Content-Type: application/json" \
-d '{
"id": "3f2a1b8c-...",
"clientSecret": "<client_secret>"
}'

Response:

{
"accessToken": "eyJhbGci...",
"expiresIn": 1800,
"tokenType": "Bearer"
}

expiresIn is the token's remaining lifetime in seconds, derived from the account's configured accessTokenExpiration.

Step 2 — Call the API with the access token

curl https://api.thirdloop.ai/api/core/projects \
-H "Authorization: Bearer eyJhbGci..."

Token handling in your integration

Access tokens are short-lived by design — the default is 60 minutes. Build your client to handle that:

  • Cache the token in memory for the duration of expiresIn. Do not call /login before every request.
  • Refresh proactively, a minute or two before expiry, rather than waiting for a 401.
  • Retry once on 401 by re-authenticating, in case the token expired between your check and the request landing.
  • Never persist tokens to disk or logs. Keep them in memory only.
  • Keep the client secret in a secret manager, injected as an environment variable or mounted secret at runtime.

Choosing a token lifetime

Shorter lifetimes limit the blast radius of a leaked token but mean more /login calls. A useful default:

  • 15–60 minutes for most integrations.
  • Minutes for high-sensitivity admin-scoped automation.
  • Hours only for low-risk read workloads where the extra token exchanges are a real cost.

Best Practices

  • One service account per integration. Separate accounts mean you can rotate, scope, and revoke each integration independently, and lastAccessed tells you something meaningful.
  • Name accounts after the system that uses them, not the person who created them — billing-sync, not alex-test.
  • Grant the narrowest scope that works. Start at read and widen only when a call actually fails.
  • Rotate secrets on a schedule and immediately whenever someone with access to the secret leaves the team.
  • Audit periodically. List all accounts, check lastAccessed, and delete anything dormant.
  • Never use a service account as a shared human credential. If a person needs API access, issue them an API key so actions remain attributable.

Reference

Full request and response schemas for every endpoint above are in the API reference under the user-service-accounts tag.