> ## Documentation Index
> Fetch the complete documentation index at: https://docs.asteroid.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Production checklist

> Publishing, API keys, notifications, outcomes, retries, concurrency, and monitoring.

Your integration works. This page covers how to get it into production.

The workflow's deploy page tracks the four steps below and shows your progress.

***

## Four steps

<Steps>
  <Step title="Publish a version">
    The API runs the published version.

    Publish again after every change you want in production. See
    [Versions and publishing](/concepts/versions).
  </Step>

  <Step title="Create an API key">
    Click your profile picture at the bottom left, then **API Keys**. Or open
    [platform.asteroid.ai/keys](https://platform.asteroid.ai/keys).
  </Step>

  <Step title="Use our SDK to integrate into your codebase">
    See our [SDKs](/sdks/setup) and our [API Reference](/api-reference/overview).
  </Step>

  <Step title="Set up notifications">
    Open the workflow's **Notifications** page. Add a webhook or a Slack channel, and subscribe to
    failures and other events that you want to be notified about.

    See [Webhooks and Slack](/operate/webhooks-and-slack).
  </Step>
</Steps>

***

## Handle outcomes

Your code must handle two groups.

1. **Every [outcome label](/concepts/inputs-and-outputs) the workflow declares.** These are the expected endings, success and failure
   alike.
2. **A label you do not recognise.** If a new outcome is added to your workflow, make sure that your code is robust enough to catch this outcome and alert, rather than crashing.

The labels below are placeholders. Replace them with the outcomes your own workflow declares.

```ts theme={null}
if (execution.status !== 'completed' || !execution.executionResult) {
  return escalate({ status: execution.status, url: execution.platformUrl });
}

switch (execution.executionResult.outcome) {
  case 'slots_found':
    return saveSlots(execution.executionResult.result);
  case 'no_slots_available':
    return scheduleRetry(execution.executionResult.result.nextAvailable);
  default:
    return escalate({ outcome: execution.executionResult.outcome, url: execution.platformUrl });
}
```

***

## Decide your retry rule

Sometimes, things happen that cause your executions to fail. The website might have updated to add a new form page that your workflow isn't expecting. The website might be experiencing an outage.

**Runs might not be idempotent.** Running a booking workflow twice books two appointments. If multiple runs of a workflow have side effects on the real world, it's worth spending some time on your retry flow.

You can tag every run with your own internal IDs using `metadata`, then search before you retry.

```bash theme={null}
# Did we already run this order?
curl "https://odyssey.asteroid.ai/agents/v2/executions?agentId=$ASTEROID_AGENT_ID\
&metadataKey=orderId&metadataValue=ORD-88213" \
  -H "X-Api-Key: $ASTEROID_API_KEY"
```

`GET /executions` also filters by `status`, `phase`, `outcomeLabel`, `triggerSource`,
`workflowVersion` and `createdAfter`. The same query sweeps for runs your webhook handler missed
while it was down.

***

## Plan for concurrency

One agent profile holds one browser session. Two executions that share a profile can overwrite each other's
state when they terminate. Both can then fail in confusing ways.

Run the same workflow against the same portal more than once at a time? You can use a profile pool and
pass `agentProfilePoolId` instead of `agentProfileId`. See [Agent profiles](/concepts/profiles).

Profile pools give each execution its own browser state or credentials. Even if the credentials are the same, having each execution use its own state is beneficial for not causing cookies to be overwritten.

Profile pools allow you to control concurrent access to a profile; you can make it so that a profile can only be in use by one execution at a time.

When the pool or your organisation's concurrency limit is full, an execute call fails by default.
Send `"onCapacityLimit": "queue"` and the platform holds the execution as `queued`, then starts it
when a slot frees. Your caller gets the execution ID at once and polls it as usual. See
[Queue when capacity is full](/integrate/call-an-agent#queue-when-capacity-is-full).

For a known list of work, you might want to use a [batch](/operate/batches). A spreadsheet of
patients or a day of claims fits this. A batch carries concurrency limits and progress tracking.

***

## Protect your API key

* Keep the key server-side. Never put it in browser or mobile code.
* Use one key per system, so revocation is easy.
* Keep keys out of logs.

See [Security](/support-security/security) for our certifications and data handling.

***

## Know where to look when it breaks

| Question                   | Where to look                                                       |
| -------------------------- | ------------------------------------------------------------------- |
| Did my request arrive?     | The **Executions** page, filtered to API-triggered executions       |
| What did the workflow do?  | The execution timeline and its session recording                    |
| Why did it decide that?    | `executionResult.reasoning`, and the reasoning in the timeline      |
| Is this execution typical? | The workflow's **Stats** page                                       |
| Which version ran?         | `workflowId` on the execution, or the version on the execution page |

[Debug your workflows](/operate/debug) walks through a broken execution step by step.

Send `platformUrl` with any support question so that our team can take a look.

***

## Related

<CardGroup cols={2}>
  <Card title="Call a workflow from your code" icon="code" href="/integrate/call-an-agent" horizontal>The whole integration on one page</Card>
  <Card title="Webhooks and Slack" icon="webhook" href="/operate/webhooks-and-slack" horizontal>Get told when an execution finishes or fails</Card>
  <Card title="Batch executions" icon="layers" href="/operate/batches" horizontal>Run one workflow over many rows</Card>
  <Card title="Debug your workflows" icon="search" href="/operate/debug" horizontal>Find out why an execution went wrong</Card>
</CardGroup>
