External AOP API — Authentication & Usage Guide (Beta)

Overview

The External AOP API lets you programmatically trigger and monitor AOP (Agent Operating Procedure) executions from your own systems. You authenticate once with OAuth client credentials, exchange them for a short-lived JWT access token, then use that token to start an AOP run and poll its status until it reaches a terminal state.

All requests use HTTPS and JSON. The API is asynchronous: the execute endpoint queues a run and returns an aop_item_id, which you use to track progress through the status endpoint.

Getting Started

Follow these four steps to get up and running:

  1. Request an OAuth app from Leena AI team. You will receive a client_id, client_secret, username, and password. Leena AI will share these with you.
  2. Generate an access token by calling POST https://<region-code>-acl.leena.ai/api/v1.0/oauth/token with your credentials.
  3. Trigger an AOP run by calling POST https://<region-code>-aic.leena.ai/api/v1/external/aop/execute with the aop_id you want to execute.
  4. (Optional) Poll the run status at GET https://<region-code>-aic.leena.ai/api/v1/external/aop/items/{aop_item_id}/status until it reaches a terminal state (completed, failed, or aborted).

How to Get the AOP ID

The aop_id required by the execute endpoint is the AOP's identifier. You can fetch it directly from the Leena AI dashboard.


Follow these steps:

  1. Sign in to the Leena AI dashboard.
  2. Navigate to AI Colleagues, then open the relevant colleague (for example, "IT Support Specialist").
  3. Open the AOP whose ID you want to retrieve. This opens the AOP details view.
  4. In the AOP details section, locate the Identifier field. The value shown there is your aop_id.
  5. Copy the value and use it as the aop_id parameter in the Execute AOP request.

Figure 1 — The Identifier value shown on the AOP details view is the aop_id used in the API.

📘

Tip

The Identifier is a human-readable slug (e.g. it_support_ticket_creation). The execute endpoint also accepts the AOP's ObjectId, but the slug shown here is usually easier to use.

Authentication

All endpoints require a Bearer JWT token in the Authorization header. Tokens are obtained via the platform's OAuth token API.

Step 1 — Register an OAuth App

Request an OAuth app from the Leena AI team. You will receive:

CredentialDescription
client_idUnique identifier for your OAuth app
client_secretSecret key for your OAuth app
usernameSystem username for the API execution
passwordPassword for the system user for the API execution

Step 2 — Generate an Access Token

Exchange your credentials for a JWT access token.

POST https://<region-code>-acl.leena.ai/api/v1.0/oauth/token

Headers

HeaderValue
AuthorizationBasic auth, with client_id as username and client_secret as password
Content-Typeapplication/json

Request Body

{
  "username": "<username-received-from-leena>",
  "password": "<password-received-from-leena>",
  "grant_type": "password"
}

Example Request

curl -X POST "https://<region-code>-acl.leena.ai/api/v1.0/oauth/token" \
  -H "Content-Type: application/json" \
  -H "Authorization: Basic <Base64-encoded-string-of-clientId:clientSecret>" \
  -d '{
    "username": "<username-received-from-leena>",
    "password": "<password-received-from-leena>",
    "grant_type": "password"
  }'

Response

{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "refresh_token": "<Refresh-token>",
  "expires_in": 3600
}

Step 3 — Use the Token

Include the token in the Authorization header for all API calls:

Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Endpoints

1. Execute AOP

Initiate an AOP execution. The request is accepted and queued for asynchronous processing.

POST https://<region-code>-aic.leena.ai/api/v1/external/aop/execute

Headers

HeaderValue
AuthorizationBearer <access_token>
Content-Typeapplication/json

Request Body

FieldTypeRequiredDescription
aop_idstringYesAOP identifier (slug) or ObjectId
message_to_startstringNoInitial message/instruction for the AOP execution (default: "")
contextobjectNoKey-value pairs passed as additional instructions to the AOP (default: {})

Example Request

curl -X POST "https://<region-code>-aic.leena.ai/api/v1/external/aop/execute" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "aop_id": "aop-1",
    "message_to_start": "Analyze the ticket shared",
    "context": {
      "ticket_number": "INC0503407",
      "state": "In Progress",
      "priority": "Priority 4",
      "impact": "3 - Low",
      "urgency": "3 - Low",
      "severity": "3 - Low",
      "category": "Request",
      "subcategory": "General Question",
      "short_description": "voucher approval",
      "description": "<Tickets-Description>",
      "comments_and_work_notes": "<comments>",
      "close_notes": "",
      "close_code": "",
      "contact_type": "Phone",
      "opened_at": "04/15/26 03:22:21",
      "closed_at": "",
      "resolved_at": "",
      "opened_by": "",
      "assigned_to": "",
      "assignment_group": "",
      "caller_id": "",
      "resolved_by": "",
      "closed_by": "",
      "location": ""
    }
  }'

Response — 200 OK

FieldTypeDescription
aop_item_idstringUnique ID of the created AOP execution item
request_idstringID of the underlying request
run_idstring | nullHuman-readable run identifier (e.g. ID_007762) for tracking
statusstringAlways "accepted" — indicates the request was queued for processing
{
  "aop_item_id": "69e90f247276b2fa8f2766e0",
  "request_id": "69e90f237276b2fa8f2766da",
  "run_id": "ID_007762",
  "status": "accepted"
}
📘

Note

"accepted" is an acknowledgement status (analogous to HTTP 202). It means the execution has been queued, not completed. Use the status endpoint to track progress.

Error Responses

StatusCondition
400Invalid request body or AOP cannot be executed
401Missing, expired, or invalid token
403Token missing required aic.aop_execute scope
404AOP identifier not found

2. Get AOP Execution Status

Poll the current status of an AOP execution.

GET https://<region-code>-aic.leena.ai/api/v1/external/aop/items/{aop_item_id}/status

Headers

HeaderValue
AuthorizationBearer <access_token>

Path Parameters

ParameterTypeDescription
aop_item_idstringThe aop_item_id returned from the execute endpoint

Example Request

curl "https://<region-code>-aic.leena.ai/api/v1/external/aop/items/69e90f247276b2fa8f2766e0/status" \
  -H "Authorization: Bearer <access_token>"

Response — 200 OK

FieldTypeDescription
aop_item_idstringID of the AOP execution item
reference_idstringHuman-readable reference (e.g. ABC000001)
statusstringCurrent execution status (see table below)
initiated_atstringISO 8601 timestamp when execution started
completed_atstring | nullISO 8601 timestamp when execution finished (null if still running)
{
  "aop_item_id": "69e90f247276b2fa8f2766e0",
  "reference_id": "RPT000042",
  "status": "in_progress",
  "initiated_at": "2026-04-22T15:50:55.000000",
  "completed_at": null
}

Execution Statuses

StatusDescription
in_progressAOP is currently executing
completedExecution finished successfully
failedExecution encountered an error
pausedExecution is paused (e.g. waiting for human input or approval)
abortedExecution was manually aborted

Error Responses

StatusCondition
400Invalid aop_item_id format
401Missing, expired, or invalid token
403Token missing required scope
404AOP item not found

Usage Pattern

A typical integration follows this flow:

1. POST https://<region-code>-acl.leena.ai/api/v1.0/oauth/token
      → get access_token

2. POST https://<region-code>-aic.leena.ai/api/v1/external/aop/execute
      → get aop_item_id (status: "accepted")

3. (Optional) GET https://<region-code>-aic.leena.ai/api/v1/external/aop/items/{id}/status
      → poll until status is terminal

Terminal statuses: completed, failed, aborted.


AIC Total Runs — Report API

The External Analytics API lets you programmatically pull AIC (AI Colleague) run-level analytics for your bot from your own systems. You authenticate with an OAuth bearer token issued by Leena AI, then call a single query endpoint with a date window and pagination parameters to retrieve a paginated list of AIC runs.

The endpoint returns the run records along with rendering metadata and pagination details. A timestamp filter is also supported for incremental polling so you only fetch runs that have been updated since your last call.

Note: Analytics data is aggregated and may be delayed by approximately 2 hours. Runs that have just completed may not appear in the response immediately; allow up to ~2 hours for new or updated runs to be reflected in the API.

Getting Started

Follow these three steps to get up and running:

  • Call GET https://<region-code>-analytics-api.leena.ai/api/analytics/query/external with the required query parameters (metricId, responseType, from, to) and optional pagination parameters.
  • Optionally — for incremental polling, persist the latest timestamp from your previous response and pass it back as filters[timestamp][gt] to fetch only runs that have changed since.

Endpoint

1. Get AIC Total Runs

Returns a paginated list of AIC runs for your bot over a date window. The endpoint is read-only and idempotent.

GET https://<region-code>-analytics-api.leena.ai/api/analytics/query/external

Headers

HeaderValue
AuthorizationBearer <access_token>
Content-Typeapplication/json

Query Parameters

ParameterTypeRequiredDescription
metricIdstringYesFixed value: aic:totalRunsTable.
responseTypestringYesFixed value: table.
fromstring (date)YesStart of the date window in YYYY-MM-DD. Filtered by run start.
tostring (date)YesEnd of the date window in YYYY-MM-DD. Filtered by run start.
pageintegerNoPage number for pagination. Default: 1.
limitintegerNoNumber of records per page. Default: 10.
filters[timestamp][gt]string (ISO 8601)NoReturns only runs updated after this timestamp. Used for incremental polling.

Example Request

curl -G 'https://<region-code>-analytics-api.leena.ai/api/analytics/query/external' \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode 'metricId=aic:totalRunsTable' \
  --data-urlencode 'responseType=table' \
  --data-urlencode 'from=2026-04-01' \
  --data-urlencode 'to=2026-05-01' \
  --data-urlencode 'page=1' \
  --data-urlencode 'limit=100'

Response — 200 OK

FieldTypeDescription
isSuccessbooleantrue if the query succeeded; false on a query-level error (the message field will explain).
result.columnsarrayRendering metadata for the table. Ignore this when consuming the data programmatically.
result.rowsarrayThe data — each element is a run record (see Row Fields below).
result.totalintegerTotal count of records across all pages for the supplied window/filters.
result.pageintegerThe page number echoed back from the request.
result.limitintegerThe page size echoed back from the request.

Example Response

{
  "isSuccess": true,
  "result": {
    "columns": [ ... ],
    "rows":    [ ... ],
    "total":   1234,
    "page":    1,
    "limit":   100
  }
}

Row Fields

Each entry in result.rows represents a single AIC run and contains the following fields:

FieldDescription
runIdUnique identifier for the run.
requestIdRequest ID behind the run; useful as a stable join key.
handledByWho handled the run — Main Orchestrator and/or AI Colleagues that participated. Array of string.
statusFinal state of the run — Handled, Unhandled, Failed, Cancelled, etc.
initiatedOnWhen the run started.
initiatedByEmployee who triggered the run — { id, name, email }.
initiatedForEmployee the run was executed for (often the same as initiatedBy).
triggerTypeHow the run was triggered — manual (chat/UI), workbench (scheduled), or api (REST).
completedOnWhen the run reached its final state. NA if still in progress.
scheduleNameName of the schedule that triggered the run, if any.
escalation"Yes" if the run was escalated, otherwise "No".
associatedSkillsSkills executed by the Main Orchestrator during the run. Array of string.
associatedAopsAOPs executed by AI Colleagues during the run. Array of string.
totalTimeTakenRun duration, pre-formatted (e.g. "2m 13s").
unhandledDueToShort reason category when the run wasn't handled (e.g. "Scope/Guardrails").
unhandledReasonTextFull free-text explanation of why the run wasn't handled.
inputTokensTotal input tokens consumed by all LLM calls in this run.
outputTokensTotal output tokens consumed by all LLM calls in this run.
guardrailGuardrail violations summary — { totalViolations, violations: [{ type, count }, ...] }.
participantsArray of participant groups — each { title, sub_title, user_list: [{ userId, displayName, email }] }. Groups include initiator, action owners, escalation contacts, and notification recipients.

Polling for New Runs — the Timestamp Filter

The timestamp field is the time when a run was last updated on Leena AI's side. Passing filters[timestamp][gt]=<value> returns only runs that have been updated after that timestamp.

If you save the latest timestamp from your previous response and pass it back as filters[timestamp][gt] on the next call, you will only receive runs that have changed — there is no need to re-pull the entire window. Use gt (greater than) so the boundary row is not returned twice.

Recommended Polling Pattern

  • On the first call, fetch the full window using from / to and page through all pages until result.total is exhausted.
  • Track the maximum timestamp value seen across the returned rows.
  • On subsequent calls, pass that maximum timestamp back as filters[timestamp][gt] to receive only updated runs.
  • Persist the new maximum timestamp from the response and repeat.

Errors

StatusConditionNotes
400Malformed query parameterReturned when a required parameter is missing or has an invalid format (e.g. invalid date).
401Missing or expired token, or wrong scopeVerify the token is valid and was issued with the api.response scope.
200 with isSuccess: falseQuery-level errorThe HTTP request succeeded but the query failed. Inspect the message field in the response body

Usage Pattern

A typical integration follows this flow:

  1. Obtain an OAuth bearer token using token api.
  2. GET https://<region-code>-analytics-api.leena.ai/api/analytics/query/external with metricId, responseType, from, to, page, limit → returns { isSuccess, result: { columns, rows, total, page, limit } }
  3. (Optionally) On subsequent polls, pass filters[timestamp][gt]=<lastSeen> → returns only runs updated since the last call.

Region Codes and Endpoints