Key Features

Ability API Endpoint Guide

Ability API Endpoint Guide

Ability API Endpoint Guide

Using the Ability API Endpoint

If you've deployed an ability (workflow) you built in Agentria as an API, external services can call its API endpoint to run the ability. The Ability API is a REST API that supports async execution, SSE (Server-Sent Events) streaming, status queries, and request cancellation.

This guide walks through how to call each endpoint and how to safely retrieve execution results.

Before You Begin


  • The ability must already be deployed as an API. If you haven't deployed it yet, complete the ๐Ÿ”—API Release guide first.

  • You'll need the endpoint code (api_endpoint) and API key issued during deployment.


The base URL and authentication method apply to every endpoint below.

Base URL

Every request requires an X-API-KEY header.

Step 1: Run an Ability Asynchronously

Runs the ability asynchronously and immediately returns a request_id (transaction ID). You can retrieve the result via the status query API (Step 3) or a callback URL.

Path Parameters

Parameter

Type

Description

api_endpoint

string

Ability code (unique identifier of the released ability)

Headers

Header

Type

Required

Description

X-API-KEY

string

Yes

API access token

Request Body (multipart/form-data)

Field

Type

Required

Description

params_json

string (JSON)

Yes

Input parameters for the ability

callback_url

string

No

Callback URL that receives the result via POST

debug

boolean

No

Debug mode (default: false)

external_request_id

string

No

Client-issued external request identifier (up to 128 characters, ^[A-Za-z0-9_\-\.:]+$). Must be unique within the same ability.

files

file[]

No

Files to upload

params_json takes a JSON object matching the input parameters defined on the ability. Fields vary by ability, so check the target ability's input spec first.

{
  "inputData": "Sample text to analyze.",
  "option1": "value1",
  "option2": 123
}
{
  "inputData": "Sample text to analyze.",
  "option1": "value1",
  "option2": 123
}
{
  "inputData": "Sample text to analyze.",
  "option1": "value1",
  "option2": 123
}

external_request_id is an identifier the client issues before sending the request, and it serves two purposes. First, if the same external_request_id is submitted again, the server treats it as the same request and rejects it with 409 Conflict, which prevents duplicate execution (idempotency) from things like network retries. Second, because the client already holds this identifier before making the call, you can track and query the request by this value even before the server returns its own request_id in the response.

Response

On success, returns 200 OK with the request ID (string).

"a1b2c3d4-e5f6-7890-abcd-ef1234567890"
"a1b2c3d4-e5f6-7890-abcd-ef1234567890"
"a1b2c3d4-e5f6-7890-abcd-ef1234567890"

Error Responses

Status

Description

400 Bad Request

params_json or external_request_id is malformed

401 Unauthorized

API key authentication failed

404 Not Found

Ability not found

409 Conflict

The same external_request_id is already registered

500 Internal Server Error

Internal server error

Example Request

curl -X POST "{host}/api/ability/my-ability" \
  -H "X-API-KEY: your-api-key" \
  -F 'params_json={"inputData": "Hello"}' \
  -F 'callback_url=https://my-service.com/webhook' \
  -F 'external_request_id=order-12345' \
  -F 'debug=false'
curl -X POST "{host}/api/ability/my-ability" \
  -H "X-API-KEY: your-api-key" \
  -F 'params_json={"inputData": "Hello"}' \
  -F 'callback_url=https://my-service.com/webhook' \
  -F 'external_request_id=order-12345' \
  -F 'debug=false'
curl -X POST "{host}/api/ability/my-ability" \
  -H "X-API-KEY: your-api-key" \
  -F 'params_json={"inputData": "Hello"}' \
  -F 'callback_url=https://my-service.com/webhook' \
  -F 'external_request_id=order-12345' \
  -F 'debug=false'

Step 2: Receive Real-Time Streaming via SSE

Runs the ability and streams each node's execution result in real time via SSE.

Path Parameters / Headers

Same as Step 1 โ€” requires the api_endpoint path parameter and the X-API-KEY header.

Request Body (multipart/form-data)

Field

Type

Required

Description

params_json

string (JSON)

Yes

Input parameters for the ability (same as Step 1)

debug

boolean

No

Debug mode (default: false)

external_request_id

string

No

Client-issued external request identifier. Same format rules as Step 1. Once the SSE stream ends, you can also query status/results by this value.

Response

The response Content-Type is text/event-stream.

The X-Ability-Request-Id response header carries the request ID. Even if the connection drops before the first event arrives, you can use this value to re-query โ€” it matches the first request_id event.

Note: The X-Request-Id header is a separate value used for HTTP tracing. Don't confuse it with X-Ability-Request-Id.

Each event is delivered in a self-defined SSE line format that is not compatible with the standard EventSource format. A blank line separates events.




event_type Values and Body Shapes

event_type

When it fires

Body shape

request_id

Right after the stream starts (first, once)

Plain string (UUID). The request ID to use for re-querying results if the stream is lost

node

Each time a node finishes executing (intermediate, repeats per node)

JSON: {"ability_node_id": , "ability_node_name": , "results": }

response

Ability completes successfully (final, once)

JSON: APIResponseSchema as-is ({"request_id":"...","status":"COMPLETED","results":{...},...})

error

Ability fails or a processing error occurs (final, once)

JSON: APIResponseSchema (status="FAILURE") or a plain string

If event_type is request_id, it's a meta event; node is an intermediate event; response or error is the final event. Alternatively, if the body JSON has a status key set to COMPLETED or FAILURE, treat it as final. The connection closes once the stream ends.

Re-Querying Results After a Lost Stream

If a network drop causes you to miss the final event, don't resend the same request โ€” that would re-run the ability. Instead, re-query, since the server completes execution and records the result regardless of the stream connection.


  • Poll GET /{api_endpoint}/{request_id}/result using the request ID from the request_id event (or the X-Ability-Request-Id header) โ€” see Step 3.

  • If you assigned an external_request_id, you can also query GET /{api_endpoint}/external/{external_request_id}/result โ€” see Step 3.


Poll until status becomes COMPLETED, FAILURE, or CANCELED. Resending with the same external_request_id is rejected with 409 Conflict, so using an external ID also protects you against accidental duplicate runs.

Example Request

curl -N -X POST "{host}/api/ability/my-ability/sse" \
  -H "X-API-KEY: your-api-key" \
  -F 'params_json={"inputData":"Hello"}' \
  -F 'debug=false'
curl -N -X POST "{host}/api/ability/my-ability/sse" \
  -H "X-API-KEY: your-api-key" \
  -F 'params_json={"inputData":"Hello"}' \
  -F 'debug=false'
curl -N -X POST "{host}/api/ability/my-ability/sse" \
  -H "X-API-KEY: your-api-key" \
  -F 'params_json={"inputData":"Hello"}' \
  -F 'debug=false'

The -N (--no-buffer) option is required โ€” without it, chunks arrive in bursts.

Example Response (Wire Format)




JavaScript Client Example

const formData = new FormData();
formData.append('params_json', JSON.stringify({ inputData: 'Hello' }));
formData.append('debug', 'false');

const response = await fetch(`${host}/api/ability/my-ability/sse`, {
  method: 'POST',
  headers: { 'X-API-KEY': 'your-api-key' },
  body: formData
});

// For re-querying via GET .../{requestId}/result if the stream is lost. Also updated by the first request_id event.
let requestId = response.headers.get('X-Ability-Request-Id');

// A single read() can return multiple events merged together, so line-buffered parsing is required.
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buf = '';
let currentEvent = null;

const events = [];

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buf += decoder.decode(value, { stream: true });

  let nl;
  while ((nl = buf.indexOf('\n')) !== -1) {
    const line = buf.slice(0, nl);
    buf = buf.slice(nl + 1);

    if (line === '') {
      currentEvent = null; // event boundary
      continue;
    }
    if (line.endsWith(':')) {
      currentEvent = line.slice(0, -1); // "node" | "response" | "error"
      continue;
    }
    // body line โ€” try JSON.parse, fall back to raw string on failure
    let body;
    try { body = JSON.parse(line); } catch { body = line; }

    if (currentEvent === 'request_id') {
      requestId = body; // plain UUID string
    } else if (currentEvent === 'node') {
      console.log('[node]', body.ability_node_id, body.ability_node_name, body.results);
    } else if (currentEvent === 'response' || currentEvent === 'error') {
      events.push(body);
      console.log(`[${currentEvent}] status=${body.status}`, body.results);
    }
  }
}

// Final result = events[events.length - 1]
const final = events[events.length - 1];
if (final?.status === 'FAILURE') {
  console.error('Failed:', final.failure_reason);
}

// Stream ended without a final event โ€” re-query instead of resending (re-running)
if (!final && requestId) {
  const res = await fetch(`${host}/api/ability/my-ability/${requestId}/result`, {
    headers: { 'X-API-KEY': 'your-api-key' }
  });
  const result = await res.json(); // Poll until status is COMPLETED or FAILURE
}
const formData = new FormData();
formData.append('params_json', JSON.stringify({ inputData: 'Hello' }));
formData.append('debug', 'false');

const response = await fetch(`${host}/api/ability/my-ability/sse`, {
  method: 'POST',
  headers: { 'X-API-KEY': 'your-api-key' },
  body: formData
});

// For re-querying via GET .../{requestId}/result if the stream is lost. Also updated by the first request_id event.
let requestId = response.headers.get('X-Ability-Request-Id');

// A single read() can return multiple events merged together, so line-buffered parsing is required.
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buf = '';
let currentEvent = null;

const events = [];

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buf += decoder.decode(value, { stream: true });

  let nl;
  while ((nl = buf.indexOf('\n')) !== -1) {
    const line = buf.slice(0, nl);
    buf = buf.slice(nl + 1);

    if (line === '') {
      currentEvent = null; // event boundary
      continue;
    }
    if (line.endsWith(':')) {
      currentEvent = line.slice(0, -1); // "node" | "response" | "error"
      continue;
    }
    // body line โ€” try JSON.parse, fall back to raw string on failure
    let body;
    try { body = JSON.parse(line); } catch { body = line; }

    if (currentEvent === 'request_id') {
      requestId = body; // plain UUID string
    } else if (currentEvent === 'node') {
      console.log('[node]', body.ability_node_id, body.ability_node_name, body.results);
    } else if (currentEvent === 'response' || currentEvent === 'error') {
      events.push(body);
      console.log(`[${currentEvent}] status=${body.status}`, body.results);
    }
  }
}

// Final result = events[events.length - 1]
const final = events[events.length - 1];
if (final?.status === 'FAILURE') {
  console.error('Failed:', final.failure_reason);
}

// Stream ended without a final event โ€” re-query instead of resending (re-running)
if (!final && requestId) {
  const res = await fetch(`${host}/api/ability/my-ability/${requestId}/result`, {
    headers: { 'X-API-KEY': 'your-api-key' }
  });
  const result = await res.json(); // Poll until status is COMPLETED or FAILURE
}
const formData = new FormData();
formData.append('params_json', JSON.stringify({ inputData: 'Hello' }));
formData.append('debug', 'false');

const response = await fetch(`${host}/api/ability/my-ability/sse`, {
  method: 'POST',
  headers: { 'X-API-KEY': 'your-api-key' },
  body: formData
});

// For re-querying via GET .../{requestId}/result if the stream is lost. Also updated by the first request_id event.
let requestId = response.headers.get('X-Ability-Request-Id');

// A single read() can return multiple events merged together, so line-buffered parsing is required.
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buf = '';
let currentEvent = null;

const events = [];

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buf += decoder.decode(value, { stream: true });

  let nl;
  while ((nl = buf.indexOf('\n')) !== -1) {
    const line = buf.slice(0, nl);
    buf = buf.slice(nl + 1);

    if (line === '') {
      currentEvent = null; // event boundary
      continue;
    }
    if (line.endsWith(':')) {
      currentEvent = line.slice(0, -1); // "node" | "response" | "error"
      continue;
    }
    // body line โ€” try JSON.parse, fall back to raw string on failure
    let body;
    try { body = JSON.parse(line); } catch { body = line; }

    if (currentEvent === 'request_id') {
      requestId = body; // plain UUID string
    } else if (currentEvent === 'node') {
      console.log('[node]', body.ability_node_id, body.ability_node_name, body.results);
    } else if (currentEvent === 'response' || currentEvent === 'error') {
      events.push(body);
      console.log(`[${currentEvent}] status=${body.status}`, body.results);
    }
  }
}

// Final result = events[events.length - 1]
const final = events[events.length - 1];
if (final?.status === 'FAILURE') {
  console.error('Failed:', final.failure_reason);
}

// Stream ended without a final event โ€” re-query instead of resending (re-running)
if (!final && requestId) {
  const res = await fetch(`${host}/api/ability/my-ability/${requestId}/result`, {
    headers: { 'X-API-KEY': 'your-api-key' }
  });
  const result = await res.json(); // Poll until status is COMPLETED or FAILURE
}

Note (Python client): The same line buffering is required. httpx.AsyncClient.stream()'s aiter_lines() yields one line at a time, so skip node: / response: / error: prefix lines with a JSON parse error and only parse JSON lines. However, the request_id event's body (a plain UUID) isn't JSON โ€” if you need the ID for re-querying, either track whether the previous line was request_id: or use the X-Ability-Request-Id response header.

Step 3: Check Request Status and Results

Queries the processing status and result of an async execution request.




Both endpoints return the same response.

Path Parameters

Parameter

Type

Description

api_endpoint

string

Ability code

request_id

string

Async request ID (returned by the execution API)

Response

On success, returns 200 OK with the result below.

{
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "external_request_id": null,
  "chat_room_id": null,
  "status": "COMPLETED",
  "failure_reason": null,
  "results": {
    "output": "Ability execution result"
  },
  "artifact_metadata_json": null,
  "request_params_json": "{\"inputData\":\"Hello\"}",
  "result_metadata_json": null,
  "requested_time": "2026-01-15T10:30:00",
  "updated_time": "2026-01-15T10:31:00"
}
{
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "external_request_id": null,
  "chat_room_id": null,
  "status": "COMPLETED",
  "failure_reason": null,
  "results": {
    "output": "Ability execution result"
  },
  "artifact_metadata_json": null,
  "request_params_json": "{\"inputData\":\"Hello\"}",
  "result_metadata_json": null,
  "requested_time": "2026-01-15T10:30:00",
  "updated_time": "2026-01-15T10:31:00"
}
{
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "external_request_id": null,
  "chat_room_id": null,
  "status": "COMPLETED",
  "failure_reason": null,
  "results": {
    "output": "Ability execution result"
  },
  "artifact_metadata_json": null,
  "request_params_json": "{\"inputData\":\"Hello\"}",
  "result_metadata_json": null,
  "requested_time": "2026-01-15T10:30:00",
  "updated_time": "2026-01-15T10:31:00"
}

external_request_id is populated only if an external ID was assigned at request time; otherwise it's null.

status Values

Value

Description

NONE

Initial state (awaiting processing)

PROCESSING

Processing

COMPLETED

Completed

FAILURE

Failed (see the failure_reason field for details)

CANCELED

Canceled

Example Request

curl -X GET "{host}/api/ability/my-ability/a1b2c3d4-e5f6-7890-abcd-ef1234567890/status" \
  -H "X-API-KEY: your-api-key"
curl -X GET "{host}/api/ability/my-ability/a1b2c3d4-e5f6-7890-abcd-ef1234567890/status" \
  -H "X-API-KEY: your-api-key"
curl -X GET "{host}/api/ability/my-ability/a1b2c3d4-e5f6-7890-abcd-ef1234567890/status" \
  -H "X-API-KEY: your-api-key"

Querying by External Request ID

If you assigned an external_request_id at request time, you can query the same response using that value instead.




Status

Description

400 Bad Request

external_request_id is malformed

404 Not Found

No request is registered under that external ID

curl -X GET "{host}/api/ability/my-ability/external/order-12345/status" \
  -H "X-API-KEY: your-api-key"
curl -X GET "{host}/api/ability/my-ability/external/order-12345/status" \
  -H "X-API-KEY: your-api-key"
curl -X GET "{host}/api/ability/my-ability/external/order-12345/status" \
  -H "X-API-KEY: your-api-key"

Step 4: Cancel a Request

Cancels an in-progress async request.

Path Parameters

Parameter

Type

Description

api_endpoint

string

Ability code

request_id

string

ID of the request to cancel

Response

On success, returns 200 OK with true.

Error Responses

Status

Description

500 Internal Server Error

Cancellation failed

Example Request

curl -X DELETE "{host}/api/ability/my-ability/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  -H "X-API-KEY: your-api-key"
curl -X DELETE "{host}/api/ability/my-ability/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  -H "X-API-KEY: your-api-key"
curl -X DELETE "{host}/api/ability/my-ability/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  -H "X-API-KEY: your-api-key"

Canceling Multiple Requests at Once

You can also cancel multiple async requests in a single call.

Parameter

Type

Required

Description

request_ids

list[string]

Yes

List of request IDs to cancel (repeated query parameter)

curl -X DELETE "{host}/api/ability/my-ability?request_ids=id-001&request_ids=id-002&request_ids=id-003" \
  -H "X-API-KEY: your-api-key"
curl -X DELETE "{host}/api/ability/my-ability?request_ids=id-001&request_ids=id-002&request_ids=id-003" \
  -H "X-API-KEY: your-api-key"
curl -X DELETE "{host}/api/ability/my-ability?request_ids=id-001&request_ids=id-002&request_ids=id-003" \
  -H "X-API-KEY: your-api-key"

Usage Flows at a Glance

Pick one of the four flows below depending on your use case.

Flow 1: Async Execution + Polling




Flow 2: Async Execution + Callback




Example callback payloads:

// Callback for a request with an external_request_id โ€” key included
{
  "request_id": "a1b2c3d4-...",
  "external_request_id": "order-12345",
  "status": "COMPLETED",
  "results": { ... },
  "failure_reason": null
}

// Callback for a request without an external_request_id โ€” key is omitted entirely
{
  "request_id": "a1b2c3d4-...",
  "status": "COMPLETED",
  "results": { ... },
  "failure_reason": null
}
// Callback for a request with an external_request_id โ€” key included
{
  "request_id": "a1b2c3d4-...",
  "external_request_id": "order-12345",
  "status": "COMPLETED",
  "results": { ... },
  "failure_reason": null
}

// Callback for a request without an external_request_id โ€” key is omitted entirely
{
  "request_id": "a1b2c3d4-...",
  "status": "COMPLETED",
  "results": { ... },
  "failure_reason": null
}
// Callback for a request with an external_request_id โ€” key included
{
  "request_id": "a1b2c3d4-...",
  "external_request_id": "order-12345",
  "status": "COMPLETED",
  "results": { ... },
  "failure_reason": null
}

// Callback for a request without an external_request_id โ€” key is omitted entirely
{
  "request_id": "a1b2c3d4-...",
  "status": "COMPLETED",
  "results": { ... },
  "failure_reason": null
}

Requests without an external ID never include the external_request_id key in the callback payload โ€” this preserves compatibility with existing integration code.

Flow 3: Real-Time Streaming




Flow 4: Canceling a Request




Next Steps

You've learned how to use the Ability API endpoints.

You can now call abilities directly from an external service and retrieve their execution results.


  • ๐Ÿ”— API Release โ€” revisit the endpoint and API key issuance steps.


Frequently Asked Questions

What is the Ability API?

The Ability API lets external services run an ability you've built in Agentria via a REST API. It supports async execution, SSE streaming, status/result queries, and request cancellation, and every request is authenticated with an X-API-KEY header.

When should I use the Ability API?

Use it when an external backend server or another service needs to receive an ability's execution result without going through the Agentria UI. For example, it fits cases where you want an event in your own service to trigger an ability, or you want the execution result reflected automatically in your own system.

How can I receive an ability's execution result?

There are three ways: poll GET /{api_endpoint}/{request_id}/status (or /result), specify a callback_url at request time to receive the result via callback once it's done, or use the /sse endpoint to stream each node's result in real time while it runs.

What happens if the real-time streaming (SSE) connection drops?

The server keeps running the ability to completion and records the result regardless of the stream connection. If the connection drops before you receive the final event, don't resend the same request โ€” poll the status/result query API using the request_id (or external_request_id) instead. Resending the same request re-runs the ability.

What do I need before using the Ability API?

The ability must already be deployed as an API, and you'll need the endpoint code (api_endpoint) and API key issued at deployment time. If you haven't deployed it yet, complete the API Release guide's deployment steps first.