The programmatic interface starts with two decisions: what to authenticate with and which resources you operate on. Both usual ways of passing a key are supported, and the choice between them is purely technical. The substantive choice is a different one - whose key it is. A user key is created in the personal dashboard, a service account key in the team settings. Automation needs the second: it has its own lifecycle, it is not tied to a person who tomorrow goes on holiday or leaves the company, revoking it at the first suspicion costs nothing, and its actions appear in logs as machine actions rather than yours. There is also a direct limitation worth knowing in advance: team administrator keys are not suitable for starting agents through the official language packages.
The resource model is simpler than it seems once you separate the durable from the one-off. An agent is a durable container: the conversation, workspace configuration, the model and settings. A run is a single prompt with its own event stream, status, result and the ability to cancel. Only one run is active per agent at any moment, and that limit is worth building into the architecture from the start rather than discovering through a busy error. In practice it means you keep the work queue on your side: either you wait for the agent to free up, or you maintain a pool of agents and distribute tasks among them.
It helps to lay the resources out in a table once: what each stores and how its lifecycle works. Below is that map - the agent, the run, artifacts, the model catalog, the repository catalog and delegated tokens. What is most often forgotten is noted separately: the model catalog must be discovered by a request rather than hard-coded, and the repository catalog is rate-limited especially strictly. The first saves you on the day a model identifier changes or disappears, the second saves you from failures under load.
| Resource | What it stores | Lifecycle |
|---|---|---|
| Agent | The conversation, workspace configuration, model and settings | Several sequential runs; archiving and deletion |
| Run | One prompt, an event stream, status, result, cancellation | Only one active run per agent |
| Artifacts | Files, screenshots, recordings, logs |
| Listing and download within the agent |
| Model catalog | Identifiers and parameters for your account | Discover by request before pinning in code |
|---|
| Repository catalog | Available connected repositories | Strict rate limits, caching required |
|---|
| Delegated token | Limited authority | A narrow scope and a short lifetime |
|---|
Rate limits deserve a separate word, because they design your integration for you. The list of repositories may be requested no more than once a minute and thirty times an hour per user, which means caching on your side is mandatory. An integration that fetches the list on every user request will hit the limit on its first busy day. The correct shape here is simple: the list is refreshed on a schedule or by an explicit command, while every other call reads a local copy and tolerates it being a few minutes stale. Such a limit is more honestly read not as an obstacle but as a hint about how the service is built: rarely changing directories are not meant to be polled in real time.
The table holds a resource people remember last - the delegated token. It is needed so that a self-hosted worker acts as an active member of the team: a one-hour user-scoped token minted with an agent-scoped key of a team service account, which neither refreshes itself nor mints further tokens. The point is to avoid handing out the main key where a narrow slice of rights and a short lifetime are enough. A short lifetime turns a leak from permanent access into an inconvenience with a known expiry date, and a narrow scope limits what can be done before that date. Of the two the narrow scope matters more: a leaked token opens one worker's slice of the work rather than the whole account.
Idempotency is a separate storyline. A network timeout after sending a create request does not prove the resource was not created: the request may have arrived while the response was lost on the way back. A naive retry in that situation produces a second agent, and then you have two processes for one business event: two branches, two pull requests and a doubled bill for the work. The correct scheme is to keep your own mapping between the event identifier and the created resources, treat a conflict as a normal response rather than an error, and retry with growing delay. The key words here are business event: idempotency at the level of a request does not save you, because after losing a response you do not know which request you are repeating.
There is also a technical detail that helps idempotency: you can pass your own agent identifier at creation. Then a repeat with the same identifier does not create a second resource but returns a conflict you already know how to handle. The convenience is not free: this approach is incompatible with passing session environment variables, so you have to choose between your own identifier and configuring the environment from the request. Such incompatibilities are better discovered at the design stage than at the moment when half the integration is already written around one of the two options.
It is worth knowing how both of the troubles described look in operation, because by symptom they get regularly mistaken for random glitches. Hitting a rate limit shows up not as a steady refusal but as a floating error under load: on a quiet day everything works, on release day part of the calls comes back rejected, and in the logs it reads as instability of an external service. Broken idempotency looks different: one event produces two nearly identical pull requests minutes apart, and the developer who sees them usually concludes somebody launched the task twice by hand. Neither reproduces on a developer's machine, so caching and deduplication are verified with load and a deliberately dropped connection rather than with hope.
The engineering conclusion is simple: a programmatic interface requires the same decisions as any integration with an external service. Separate credentials with a narrow scope, caching where hard limits exist, idempotency at the level of a business event and treating conflicts as a normal path. All of that is written once and then saves weeks of untangling duplicates and limits.
The typical failures are predictable. Using a personal key in shared automation. Hard-coding model identifiers instead of requesting the catalog. Fetching the repository list on every request and hitting the limit. Handing the main key to subsystems instead of delegated tokens. And retrying creation after a timeout without mapping it to a business event.