Security and per-workspace isolation

The security of AI Tasker is not a list of labels: it is an answer to four questions. Who can read what, with which credential, for how long, and with what record left behind. This guide describes the model as it is implemented in the backend and cross-checked against the public contract of the service on 21 October 2026: per-workspace isolation, encryption of credentials at rest, key rotation, the role of the human in privilege decisions, the audit feed with double redaction and the quotas that close the perimeter. Every error code, every figure and every environment variable name in this text comes from the service code, not from a marketing description.

Reviewed · Published

The per-workspace isolation model

The unit of isolation is the workspace, not the account and not the agent. An API key belongs to a workspace: the one resolved from apiKey.workspaceId plus, if there are any, the shared workspaces listed in apiKey.workspaceIds. Everything that key reaches sits inside that perimeter. There is no path through which a key can enumerate somebody else’s workspaces, and the check happens in the data layer: switching views in the dashboard does not widen the access.

The credential travels in Authorization: Bearer atkr_… or in x-api-key: atkr_…, and the server does not compare it against a value stored in clear text: it looks it up by its sha256 hash. Two behaviours follow from that, and it is worth telling them apart, because they are the difference between "you do not have permission" and "you do not exist".

  • 401 INVALID_API_KEY: the key is revoked (revokedAt) or the server does not recognise it. The request is cut at authentication, before it touches any collection.
  • 403 API key workspace unavailable: the key is valid, but the workspace it points to does not resolve. The request passes authentication and is stopped at workspace resolution.
  • 403 WORKSPACE_ARCHIVED: the key is valid and its workspace exists, but the workspace is archived by the lifecycle (7 days without activity). The response body points at the recovery route: POST /agent/lifecycle/recovery-token, authenticated with the agent’s own API key.

A 403 WORKSPACE_ARCHIVED is not a credential problem: the key is still valid and the workspace is still there. It is the state of the workspace that blocks normal access, and the response body says how to request the recovery token, which is single-use and lives for 900 s. The recovery window stays open for 7 more days from the archive.

The data hierarchy is the usual one (workspace → project → task → subtask), but what decides access is not the node the data sits in, it is the credential that arrives: an agent with a key of its own workspace writes in its workspace and nowhere else, even if it knows the shape of a foreign identifier.

What actually constrains an agent

It is worth separating what an API key declares from what the server enforces. Today, an agent is constrained by five things: (1) the workspace its key belongs to, (2) the per-workspace quotas, (3) revocation of the key, (4) the block on archived workspaces and (5) human authorisation to raise limits or take ownership of the workspace.

Scopes exist and are stored on the key: tasks:read, tasks:write, sprints:read, sprints:write and ai:generate. They are visible in the dashboard and in GET /api/apikeys, and by default a key created by a person carries tasks:read, tasks:write and ai:generate, while the registration of an autonomous agent carries tasks:read, tasks:write and sprints:read. But they are not enforced today in any route: there is no middleware and no endpoint that checks them, and the MCP server does not filter the 40 tools by scope, so tools/list returns all 40 with any valid key. A scope is the declared intent of the credential, not a wall.

If you are looking for a technical limit rather than a statement of intent, the wall is the workspace plus the quotas. Permissions and scopes have their own guide.

Credentials at rest

Two details support the static half of the model. The first: the API key is never stored in clear text. It is looked up by its sha256 hash (hashApiKey), so neither the database storage nor an improper read of that database returns a usable credential. The second: when the registration of an agent stores its credential, that credential is encrypted with AES-256-GCM (services/agentCredentialEncryption.js), with a random 12-byte IV and a 32-byte key in hexadecimal taken from the AGENT_CREDENTIAL_ENCRYPTION_KEY environment variable.

  • Random 12-byte IV: encrypting the same text twice produces two different ciphertexts, so encrypted credentials cannot be compared against each other to work out whether they are equal.
  • 32-byte key in hexadecimal: 64 characters that come from the service configuration, not from the database, and that no endpoint returns.
  • Fail closed: if AGENT_CREDENTIAL_ENCRYPTION_KEY is not configured, the service neither encrypts in clear text nor carries on without encrypting: it answers with the AGENT_CREDENTIAL_ENCRYPTION_NOT_CONFIGURED error.

A missing encryption key is a configuration failure and the default behaviour is to stop. A service that stored credentials unencrypted because an environment variable was missing would be an incident, which is why the default path is to close the door.

Two authenticated reads, using the two ways of sending the credential. The key travels in the header and the workspace is resolved from it alone.

curl https://api.ai-tasker.theboomer.dev/agent/lifecycle/status \
  -H "Authorization: Bearer atkr_…"

curl https://api.ai-tasker.theboomer.dev/api/apikeys \
  -H "x-api-key: atkr_…"

Key rotation

A credential that does not expire is a credential that eventually leaks. Rotation is designed as a two-handed operation: the agent requests a single-use token and a person with a Clerk session completes it. The agent does not rotate its own credential, and that asymmetry is the point: whoever can read the workspace cannot decide on their own how long the credential stays alive.

  1. Request: POST /agent/key-rotation-token, authenticated with the agent’s API key, issues a single-use token with 900 s of validity. That token does not rotate anything yet: it opens the door to the confirmation.
  2. Preview: POST /api/agent-key-rotations/preview, with a Clerk session, shows the data of the agent and of the workspace involved without revealing the new key.
  3. Confirmation: POST /api/agent-key-rotations/confirm, with a Clerk session, completes the rotation and returns the new API key, which is shown once.
  4. Direct rotation: POST /api/agent-key-rotations/{apiKeyId}/rotate, with an Idempotency-Key header, performs the rotation in a single step from a person’s client and is idempotent.

The rotation changes the hash of the key and its prefix. The old key stops validating immediately: the next request that uses it gets 401 INVALID_API_KEY, the same response a never-registered key gets. It is a real rotation, not an alias of the previous one.

The human in the loop

The half of the model that is not code is governance. Raising an agent’s limits, taking ownership of a workspace and approving a billable unit are actions of a person, not of a process. None of them is granted by a redirect: the autonomous registration of an agent arrives with humanLinkRequired: true, which means the registration itself grants no privilege, and the link to a person is confirmed in endpoints authenticated with Clerk.

  • Raising limits: the per-workspace quota is changed by a person from their session. An agent that hits the ceiling cannot raise it for itself or ask for it to be raised.
  • Taking ownership: POST /api/agent-links/preview and POST /api/agent-links/claim require a Clerk session. The preview does not consume the token: it lets you review identity, workspace and role before confirming the claim.
  • Approving the payment: the 2.00 €/month billable unit is approved when a person is accepted, an agent API key is created or an owner workspace is added. Consumption is metered by operation, and retries caused by a platform failure are not charged.

The documented exception is x402: an isolated MCP call can be settled with credits without a prior human account. It is the payment route that does not go through a human confirmation, and it does not grant access to a workspace either, because it is a single call and not a credential.

The audit feed and the double redaction

Every authenticated request leaves a usage row behind. The ApiKeyUsage record stores apiKeyId, workspaceId, the method, the path and the channel — human, cli, llm or agent — so that you can reconstruct who touched what and from where, without depending on the party itself to declare what it did.

The activity feed applies two redaction layers (services/activityRedaction.js): an allowlist one at write time and a deep one at read time. The first decides what is stored; the second reviews what was stored before returning it.

  • passwords and complete API keys;
  • Authorization headers and cookies;
  • JWT and OAuth or session tokens;
  • OTP and reset codes;
  • registration, link and rotation tokens;
  • webhook secrets;
  • private keys;
  • credential ciphertext;
  • x402 signatures and payloads.

What survives redaction is deliberately the non-secret part: the event identifier, the apiKeyId, the actor (the userId or the channel), the correlationId, the dates and the field labels. That is what makes it possible to audit the trail without forwarding the sensitive material.

Redaction is fail-safe. If it fails (a getter that throws, a cycle, an unreadable structure), the feed returns a minimal skeleton without the raw payload and with redactionFailed=true. The content is never written to logs: when redaction cannot be completed, the system returns less information, not more.

Quotas, the last line of defence

The limits are applied per workspace. Two different things are counted and it is worth not mixing them up: the daily ceiling of authenticated requests and the semantic monthly quota of operations.

Per-workspace quotas of the free plan, with the name they carry in the code.
LimitValueWhat it counts
dailyAgentOps200 per dayRequests authenticated with an API key, through MCP and over direct HTTP
Agent operations100 per monthReads and mutations of a collection (AGENT_READ, AGENT_MUTATION, 1 unit each)
Hosted AI operations100 per monthPlanning a sprint, generating tasks and breaking tasks down (hosted_ai bucket)
dailyHostedAiOps10 per dayDaily calls to hosted AI
Webhooks3 per workspaceRegistered webhook endpoints
webhooksPerMinute2 per minuteWebhook deliveries per minute
Activity retention30 daysActivity kept for the workspace

The difference between the two limits is the key to reading a quota error correctly. dailyAgentOps counts requests: it is the maximum number of authenticated requests per day, and the official note from the product is explicit that it is not the semantic monthly quota. The quota of 100 operations per month counts semantic operations. And there are operations that consume zero units: the webhook delivery (WEBHOOK_DELIVERED), the retry after a 5xx or a timeout (RETRY), the human read in the dashboard (HUMAN_READ) and the MCP call paid through x402 (X402_MCP_PAID_TOOL). An agent can therefore spend requests without spending semantic quota.

The order of the checks is fixed too: first the agent quota (reserveAgentQuota, with its requests counter) and then the daily request ceiling of the workspace. That is why both limits are always checked in that order, and why the catalogue of operations published by the API, with the name of each operation, is the reliable way to know what is being charged.

The limits are applied per workspace, not per person and not per API key. The free plan is 1 workspace, 1 human and 2 API keys: two agent credentials is the maximum per workspace on that plan.

Webhooks: signature, no retry and secrets

Webhooks are the outbound channel of the system towards third parties and, for that same reason, the point where data can escape to a third party. The body is signed with HMAC-SHA256 using the webhook secret and is delivered in the x-ai-tasker-signature: sha256=<signature> header, together with x-ai-tasker-event and x-ai-tasker-event-id, which is the UUID of the event; the id field of the body repeats that same UUID, so the receiver can correlate without parsing the header.

  • 10 s timeout per delivery and attempts fixed at 1: there is no automatic retry, and the retry is decided by whoever receives the delivery.
  • History: GET /api/webhooks/:id/deliveries returns the last 100 deliveries with their status (pending, delivered, failed), their responseCode and their error.
  • Events: task.created, task.updated, task.moved and task.archived; the test endpoint fires webhook.test.
  • The secret is redacted: webhook secrets are on the list of what the audit feed removes on write and on read.

Control, what it protects and how it fails

The controls of the model, what each one protects and how the failure shows up when it triggers.
ControlWhat it protectsHow it fails
Resolution by workspaceThat a key does not reach the data of other workspaces403 API key workspace unavailable when the workspace does not resolve
Revocation of the keyThat a stolen credential stops working401 INVALID_API_KEY from the moment it is revoked
Workspace lifecycleThat an inactive workspace stops being reachable403 WORKSPACE_ARCHIVED, with the recovery route in the body
Encryption at restThat the stored credential cannot be readError AGENT_CREDENTIAL_ENCRYPTION_NOT_CONFIGURED if the key is missing
Key rotationThat a credential does not live indefinitelyThe old key gets 401 INVALID_API_KEY after the rotation
Redaction of the feedThat the trail does not carry secretsMinimal skeleton with redactionFailed=true if redaction fails
Per-workspace quotasThat an agent consumes without limitThe request counter or the operation quota runs out
Human authorisationThat no process widens its own accessThe operation requires a Clerk session and is not granted by a redirect

Checklist for hardening an agent workspace

  • One API key per agent, not shared between processes and not shared between environments.
  • One workspace per agent, so that isolation is also the quota boundary.
  • Quotas reviewed and tuned to the real work of the agent, not left at their default value.
  • Rotation scheduled and performed with the current key, and not left for the day there is an incident.
  • Key revoked as soon as the agent finishes its work, and not "just in case".
  • Workspace archived after the inactivity, letting the recovery window run before purging.
  • Audit feed reviewed: what each key asked for, with which channel and from which path.
  • Webhook secrets rotated and kept out of the code that uses them.

How to verify the model in your own environment

None of this has to be taken on trust: it is checked with four reads and one test rotation. Enumerate the declared keys, read the lifecycle status, review the activity feed and check what a call with a revoked credential returns.

  1. GET /api/apikeys with a valid key: it returns the keys declared for the workspace, with their scopes and their state. It is the inventory of what has to be rotated.
  2. GET /agent/lifecycle/status with the agent’s API key: it returns the lifecycle deadlines (warning, archive, recovery) and the current state.
  3. Review the activity feed: every row should carry apiKeyId, workspaceId, method, path and channel, and no secrets. If an Authorization header or a token shows up, redaction is not doing its job.
  4. Rotate a test key and use the old one: the answer should be 401 INVALID_API_KEY and not a generic error.
  5. Check an archived workspace: the answer should be 403 WORKSPACE_ARCHIVED and the body should point at the recovery route.

Harden an agent workspace in four steps

The minimum path to leave an agent workspace with its own credential, scheduled rotation, a reviewed quota and an auditable trail. Every step is a real endpoint of the public contract of the service.

  1. Create a credential of its own for the agent

    Register the agent with POST /agent/register (Idempotency-Key header) and keep the key it returns, with the atkr_ prefix. That is the credential that travels in Authorization: Bearer atkr_…, and everything else in this guide rests on it.

  2. Check the perimeter with two reads

    With GET /api/apikeys look at the keys declared for the workspace, and with GET /agent/lifecycle/status at the lifecycle deadlines of the agent. If there is more than one credential for a single agent, this is the moment to revoke it.

  3. Put the rotation on the calendar

    Request the token with POST /agent/key-rotation-token (900 s, single use) and complete the rotation with POST /api/agent-key-rotations/confirm under a Clerk session, or with POST /api/agent-key-rotations/{apiKeyId}/rotate and an Idempotency-Key header. Store the new key: it is shown once, and the old one answers 401 INVALID_API_KEY.

  4. Review the trail and the quotas

    Pull the activity feed and check that every row carries apiKeyId, workspaceId, method, path and channel, with no secrets. Compare the volume against the workspace counters: 200 authenticated requests per day (dailyAgentOps), 100 agent operations per month and 100 hosted AI operations per month.

Frequently asked questions

Can an agent see the data of another workspace?

No. An API key resolves a single perimeter: the workspace of apiKey.workspaceId plus the shared workspaces listed in apiKey.workspaceIds. Whatever is not on that list is not reachable, and if the workspace does not resolve the answer is 403 API key workspace unavailable instead of an empty list. The scopes of the key are the declared intent and are not checked in any route today, so a scope removes no tool: the technical limit is the workspace plus the quotas.

What happens if the credential encryption key is not configured?

The service fails closed with the AGENT_CREDENTIAL_ENCRYPTION_NOT_CONFIGURED error. There is no degraded mode that stores the agent credential unencrypted: the absence of AGENT_CREDENTIAL_ENCRYPTION_KEY cuts the operation instead of carrying on with a clear-text store.

How do I know if an agent has exceeded a limit?

There are two different counters and it is worth knowing which one ran out. dailyAgentOps is the maximum number of authenticated requests per day per workspace (200), and the semantic monthly quota is 100 agent operations and 100 hosted AI operations. Every authenticated request also leaves a usage row with its method, its path and its channel, so the feed shows the real volume and what it corresponds to.

Sources

Figures verified against the live API and each product’s official page on 21 October 2026. Third-party prices can change: check the official page before you commit.

Related guides

This guide exists to be indexed and cited. It is deliberately not in the menu.