Set up a service account for the API
Last updated: October 12, 2026
Give a pipeline, a build server or a scheduled script its own identity in your workspace, with its own role and keys, so it can call Layer's REST API without borrowing a person's token and keeps working when people change roles or leave.
Before you start
You need a role that can open workspace Settings and edit members, such as Admin. Without it, Service Accounts is not listed in Settings.
You can only give a service account a role whose permissions you hold yourself, and only create keys for an account whose role you hold.
Decide what the integration will do (generate, upload, run workflows, read usage) so you can give it the smallest role that covers it.
Have somewhere safe to store the key, such as your CI system's secrets or a secret manager. Layer shows each key once.
How a service account works
Account
A service account is a machine identity that belongs to the workspace, not to a person, so it keeps working when people change roles or leave. Anything it generates spends the workspace's Creative Units, like a member's run.
Role
The account holds a role in the workspace, the same roles your members hold. The role decides what it may do. Change the role on the Service Accounts page and every key the account holds follows at once.
Keys
A key is the credential your code sends. Keys carry no permissions of their own: each one acts as the account, with the account's role. An account can hold several keys at a time, so you can give each machine its own key, and rotate one without touching the others. Every key expires, at most a year after it is created.
Scopes
For tighter control, your code can exchange a key for a short-lived access token limited to the parts of the API it uses, such as generation:write or library:read. The token can never do more than the account's role allows. The full list of scopes is in the authentication guide.
What a service account cannot do
A service account calls the REST API and the MCP servers. It cannot sign in to the Layer app, and it can never manage members, roles, keys or other service accounts, whatever role it holds.
Create a service account in Layer
Click your workspace name in the sidebar and choose Settings. Under Integrations, select Service Accounts. The page lists every service account the workspace owns, with its role, status and number of keys.
Select Create service account and enter a Name your team will recognise, named after what uses it, for example Nightly asset pipeline.
Choose its Role. New accounts start as Artist, which can generate but not administer the workspace. Select Create service account. The account joins the list as Active, with no keys yet.
Select the arrow at the start of its row to show its keys, then select Create key. Enter a Key name that says where the key will live, for example CI runner, and choose when it Expires in.
Select Create key. Layer shows the key, which starts with
layer_sak_. Copy it with the copy button and store it in your pipeline's secret store now: you won't be able to see it again.Select Done. The key is listed with the last characters of its value, its status, the date it expires and when it was Last used.
If your code will exchange the key for a short-lived token, open the account's ... menu and choose Copy ID. The ID is the account's
client_idin the exchange below. Store it next to the key; it is not a secret.
Use it with the REST API
The examples below use curl and Python, and assume the key and the account's ID are in environment variables:
export LAYER_SERVICE_ACCOUNT_KEY="layer_sak_..."
export SERVICE_ACCOUNT_ID="the ID you copied"Every endpoint, parameter and response is documented in the API reference.
Call the API with the key
The simplest way: send the key itself as a bearer token. This lists the workspaces the account belongs to, with each one's workspace_id and Creative Unit balance:
curl https://api.app.layer.ai/api/v2/workspaces \
-H "Authorization: Bearer $LAYER_SERVICE_ACCOUNT_KEY"Copy the workspace_id of your workspace into WORKSPACE_ID for the next examples.
Exchange the key for a short-lived token
For a token that expires on its own and can only reach the parts of the API your job uses, exchange the key with the OAuth 2.0 client credentials grant. The account's ID is the client_id and the key is the client_secret; scope is optional, and leaving it out gives the token every scope the role allows.
curl https://api.app.layer.ai/api/oauth/token \
-d grant_type=client_credentials \
-d client_id="$SERVICE_ACCOUNT_ID" \
-d client_secret="$LAYER_SERVICE_ACCOUNT_KEY" \
-d scope="generation:write library:read"The response holds an access_token to send as Authorization: Bearer ..., and expires_in, the seconds until it expires. There is no refresh token: when it expires, exchange the key again.
Example: generate an image
List the base models your workspace can use and pick one's base_model_id:
curl https://api.app.layer.ai/api/v2/workspaces/$WORKSPACE_ID/base-models \
-H "Authorization: Bearer $LAYER_SERVICE_ACCOUNT_KEY"Check what the run will cost before you start it. The response includes the estimate in Creative Units and whether the workspace has enough:
curl -X POST https://api.app.layer.ai/api/v2/workspaces/$WORKSPACE_ID/inferences/estimate \
-H "Authorization: Bearer $LAYER_SERVICE_ACCOUNT_KEY" \
-H "Content-Type: application/json" \
-d '{"base_model_id": "BASE_MODEL_ID", "prompt": "Stone fortress gate, game prop, isometric", "width": 1024, "height": 1024}'Start the run. It returns at once with an inference_id. The Idempotency-Key is any value unique to this run; send the same one if you retry the request:
curl -X POST https://api.app.layer.ai/api/v2/workspaces/$WORKSPACE_ID/inferences \
-H "Authorization: Bearer $LAYER_SERVICE_ACCOUNT_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: fortress-gate-build-1842" \
-d '{"base_model_id": "BASE_MODEL_ID", "prompt": "Stone fortress gate, game prop, isometric", "width": 1024, "height": 1024}'Poll it until status is complete (or failed). A complete run lists its outputs, each with a download url:
curl https://api.app.layer.ai/api/v2/workspaces/$WORKSPACE_ID/inferences/INFERENCE_ID \
-H "Authorization: Bearer $LAYER_SERVICE_ACCOUNT_KEY"Example: a Python pipeline step
The same flow as a script a pipeline can run: exchange the key for a token limited to generating, start a run, wait for it, and download the results.
import os
import time
import uuid
import requests
API = "https://api.app.layer.ai/api"
WORKSPACE_ID = os.environ["WORKSPACE_ID"]
def access_token() -> str:
response = requests.post(
f"{API}/oauth/token",
data={
"grant_type": "client_credentials",
"client_id": os.environ["SERVICE_ACCOUNT_ID"],
"client_secret": os.environ["LAYER_SERVICE_ACCOUNT_KEY"],
"scope": "generation:write",
},
timeout=30,
)
response.raise_for_status()
return response.json()["access_token"]
session = requests.Session()
session.headers["Authorization"] = f"Bearer {access_token()}"
idempotency_key = str(uuid.uuid4())
run = session.post(
f"{API}/v2/workspaces/{WORKSPACE_ID}/inferences",
json={
"base_model_id": os.environ["BASE_MODEL_ID"],
"prompt": "Stone fortress gate, game prop, isometric",
"width": 1024,
"height": 1024,
},
headers={"Idempotency-Key": idempotency_key},
timeout=30,
)
run.raise_for_status()
inference = run.json()
while inference["status"] == "in_progress":
time.sleep(inference.get("poll_interval_seconds") or 2)
poll = session.get(
f"{API}/v2/workspaces/{WORKSPACE_ID}/inferences/{inference['inference_id']}",
timeout=30,
)
poll.raise_for_status()
inference = poll.json()
if inference["status"] != "complete":
raise SystemExit(f"Run ended {inference['status']}: {inference.get('error')}")
for index, output in enumerate(inference["outputs"]):
extension = output["content_type"].split("/")[-1]
with open(f"fortress-gate-{index}.{extension}", "wb") as file:
file.write(requests.get(output["url"], timeout=60).content)Revoke a key from code
A pipeline that finds its key exposed can end it at once, authenticating as the account the same way as the exchange:
curl https://api.app.layer.ai/api/oauth/revoke \
-d client_id="$SERVICE_ACCOUNT_ID" \
-d client_secret="$LAYER_SERVICE_ACCOUNT_KEY" \
-d token="$LAYER_SERVICE_ACCOUNT_KEY"Revoking a key also ends every access token exchanged for it. An access token cannot be revoked on its own; disable or revoke the key it came from.
Check that it works
Run the
/v2/workspacescall above. It should list your workspace. A401means the key is wrong, disabled, revoked or expired.Back on Service Accounts, show the account's keys. The key's Last used date shows it has authenticated.
Rotate a key
Rotate keys before they expire, on a schedule your security policy sets, and straight away if one may have leaked. Because keys carry no permissions of their own, a new key does exactly what the old one did.
On Service Accounts, show the account's keys and find the one to replace. Expires and Last used tell you which key is due and whether it is still in use.
Select Create key, name the new key and choose when it Expires in.
Select Create key, copy the new key and put it in your pipeline's secret store in place of the old one. Select Done; both keys are now Active.
Once the pipeline has run on the new key (its Last used date moves), open the old key's ... menu and choose Revoke key.
Confirm with Revoke key. The old key shows Revoked and stops working for good; it cannot be turned back on.
Manage a service account
Change its role
Pick a new role from the Role menu on the account's row. It applies to all of its keys at once.
Pause a key or the account
In a key's ... menu, Disable key stops that key until you choose Enable key. In the account's ... menu, Disable service account stops all of its keys, and the access tokens exchanged for them, until you enable it again. Use these while you investigate a possible leak; revoke when you are sure.
Rename it
Rename in the account's ... menu changes only the label. Its role and keys stay as they are.
Delete it
Delete service account revokes every one of its keys and removes it from every workspace it belongs to. Anything using those keys stops working immediately, and this cannot be undone.
Production tips
One account per integration. Give the nightly build, the store export script and the content tool each their own account. You can then change one's role, or switch it off, without touching the others.
One key per machine or environment. Name keys after where they live (CI runner, Staging, Render farm), so a leaked key is revoked without stopping everything else.
Smallest role, narrowest token. Give the account the least role that covers its job, and have your code ask for only the scopes it uses. A job that only reads the library never needs
generation:write.Estimate before you spend. Have batch jobs call the estimate endpoint first and stop when
has_sufficient_creative_unitsis false, rather than failing halfway through a batch. A usage limit on a group is a separate cap; see Set a usage limit for a group.Make retries safe. Send an
Idempotency-Keyheader on every request that starts a run, so a retried request after a network error returns the first run instead of starting (and paying for) a second.Put rotation in the calendar. Pick an expiry your policy allows and rotate a week or two before it, using the steps above. A key that expires mid-run fails the next request with
401.Keys stay on the server. Never put a key in a game client, a web page, a plugin you ship or a public repository. Layer keys have a fixed
layer_prefix, so secret scanners can recognise a leaked one.
If something goes wrong
The API answers 401 Unauthenticated. The key was mistyped, or it is disabled, revoked or expired, or the account is disabled. Check the key's status on Service Accounts, and create a new key if it has ended.
The API answers 403 Forbidden. The account's role does not allow the call, or the workspace in the URL is not one it belongs to. Give it a role that covers the call. If the response names
insufficient_scope, the access token was exchanged for fewer scopes than the call needs: add that scope to the exchange.The token exchange answers
invalid_client. Theclient_idis not the ID of the account that owns the key, or the key no longer authenticates. Copy the ID again with Copy ID on the same account."A service account cannot be given a role with permissions you do not hold" or "You cannot issue credentials for a service account that holds permissions you do not". You can only grant, and create keys for, access you have yourself. Ask someone with a higher role, such as a workspace owner.
"A principal can hold at most ... active keys of this kind; revoke one first". The account holds as many live keys as it can. Revoke keys that are no longer used, then create the new one.