Skip to main content

Bot Execute

Invoke a Medplum Bot using the custom $execute operation.

If you're not familiar with Medplum Bots, you may want to review Bot Basics documentation first.

Invoke a bot by ID

Invoke a bot by ID when you know the Bot's ID in advance.

Finding your Bot Id

You can find the id of your Bot by clicking on the Details tab of the Bot resource. In this example, it is 43ac3060-ff20-49e8-9682-bf91ab3a5191

Find your Bot ID

Using POST

POST [base]/Bot/[id]/$execute

Examples

The MedplumClient TypeScript class provides a executeBot convenience method

const result = await medplum.executeBot(id, { input: { foo: 'bar' } }, ContentType.JSON);
console.log(result);

Using GET

Query parameters will be passed to the bot as type Record<string, string> (see Content Types below)

GET [base]/Bot/[id]/$execute?params
const getResult = await medplum.get(medplum.fhirUrl('Bot', id, '$execute').toString() + '?foo=bar');
console.log(getResult);

Invoke a bot by identifier

Sometimes you may not know the Medplum Bot ID in advance. In that case, you can invoke a Bot by Identifier.

This is also useful when the same conceptual bot exists in multiple Medplum projects. Each bot will have a different ID, but they can all have the same identifier.

Using POST

POST [base]/Bot/$execute?identifier=[system]|[code]

Examples

The MedplumClient executeBot convenience method supports both id: string and identifier: Identifier:

const result = await medplum.executeBot(
{
system: 'https://example.com/bots',
value: '1234',
},
{
foo: 'bar',
}
);
console.log(result);

Using GET

Query parameters will be passed to the bot as type Record<string, string> (see Content Types below)

GET [base]/Bot/$execute?identifier=[system]|[code]&params
const getResult = await medplum.get(
medplum.fhirUrl('Bot', '$execute').toString() + '?identifier=https://example.com/bots|1234&foo=bar'
);
console.log(getResult);

Content Types

Medplum Bots support a variety of input content types. Specify the input content type using the standard Content-Type HTTP header, or as an optional parameter to MedplumClient.executeBot().

Content-Typetypeof event.inputDescription
text/plainstring<INPUT_DATA> is parsed as plaintext string
application/jsonRecord<string, any><INPUT_DATA> is parsed as JSON-encoded object
application/x-www-form-urlencoded Record<string, string><INPUT_DATA> is parsed as URL-encoded string, resulting in a key/value map
application/fhir+jsonResource<INPUT_DATA> is parsed as a FHIR Resource encoded as JSON
x-application/hl7-v2+er7HL7Message<INPUT_DATA> is a string that should be parsed as a pipe-delimited HL7v2 message. HL7v2 is a common text-based message protocol used in legacy healthcare systems

The input data that will be parsed according to CONTENT_TYPE and passed into your Bot as event.input.

Asynchronous Execution

To run bots asynchronously, you can specify the Prefer: respond-async header to move execution of the bot to the background. Asynchronous execution will result in an HTTP 202 Accepted response immediately, and the bot will continue running in the background.

Making an Async Request

Add the Prefer: respond-async header to any bot execution request:

const response = await medplum.executeBot('your-bot-id', { foo: 'bar' }, ContentType.JSON, {
headers: {
Prefer: 'respond-async',
},
});

Checking Job Status

When using async execution, the server returns a 202 Accepted response with a Content-Location header containing the URL to check the job status:

HTTP/1.1 202 Accepted
Content-Location: https://api.medplum.com/fhir/R4/job/[job-id]/status

You can poll this URL to check the status of your bot execution. In general, for long running jobs it's best to poll no more frequently than 1 second.

The MedplumClient provides helper utilities to poll job status automatically

const response = await medplum.executeBot('your-bot-id', { foo: 'bar' }, ContentType.JSON, {
headers: {
Prefer: 'respond-async',
},
pollStatusOnAccepted: true, // Poll the status until completion
pollStatusPeriod: 2000, // Poll every 2 seconds
});

However, you can also manually poll the status URL

// Example: Get the status URL from the Content-Location header
const accessToken = 'your-access-token';
const response = await fetch('https://api.medplum.com/fhir/R4/Bot/bot-id/$execute', {
headers: {
Prefer: 'respond-async',
Authorization: `Bearer ${accessToken}`,
},
});

// Get the status URL from the Content-Location header
const statusUrl = response.headers.get('Content-Location');
if (!statusUrl) {
throw new Error('No Content-Location header found');
}

// Poll the status until completion, using a for loop with max attempts
let result: any = undefined;
for (let attempt = 1; attempt <= 30; attempt++) {
const statusResponse = await fetch(statusUrl, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});

const asyncJob = await statusResponse.json();

if (asyncJob.status === 'completed') {
// Extract the result from the output parameters
const responseBody = asyncJob.output?.parameter?.find((p: any) => p.name === 'responseBody');
result = responseBody?.valueString || responseBody?.valueBoolean || responseBody?.valueInteger;
} else if (asyncJob.status === 'error') {
// Handle error case
const outcome = asyncJob.output?.parameter?.find((p: any) => p.name === 'outcome');
throw new Error(`Bot execution failed: ${outcome?.resource?.issue?.[0]?.details?.text}`);
}

// Wait 2 second before polling again
await sleep(2000);
}

// If the result is undefined, throw an error
if (result === undefined) {
throw new Error('Max polling attempts reached without completion');
}

AsyncJob Resource Structure

The status endpoint returns an AsyncJob resource with the following key fields:

  • status: Current job status

    • accepted - Job has been queued
    • active - Job is currently running
    • completed - Job finished successfully
    • error - Job failed
    • cancelled - Job was cancelled
  • output: Contains the results when status is completed or error

    • For successful bot execution: parameter[0].name will be responseBody
    • For failed execution: parameter[0].name will be outcome with error details

Example Response

When the bot execution completes successfully:

{
resourceType: 'AsyncJob',
id: 'job-id',
status: 'completed',
request: 'https://api.medplum.com/fhir/R4/Bot/:bot-id/$execute',
requestTime: '2023-01-01T00:00:00.000Z',
transactionTime: '2023-01-01T00:00:05.000Z',
output: {
resourceType: 'Parameters',
parameter: [
{
name: 'responseBody',
valueString: 'Bot execution result',
},
],
},
};

When the bot execution fails:

{
resourceType: 'AsyncJob',
id: 'job-id',
status: 'error',
request: 'https://api.medplum.com/fhir/R4/Bot/:bot-id/$execute',
requestTime: '2023-01-01T00:00:00.000Z',
transactionTime: '2023-01-01T00:00:05.000Z',
output: {
resourceType: 'Parameters',
parameter: [
{
name: 'outcome',
resource: {
resourceType: 'OperationOutcome',
issue: [
{
severity: 'error',
code: 'processing',
details: {
text: 'Bot execution failed',
},
},
],
},
},
],
},
};