# Portal API

## Incoming Webhooks

The request shown here is used to start tasks from external systems.

Its operation proceeds as follows. The portal receives the webhook after which it passes it to the job which the runner retrieves, as the webhook was passed from the very beginning therefore the executed script has access to the data passed by the webhook.

### Creating webhook

To create a webhook you must edit the workflow in which you want to add it. Once there, you need to find the triggers section and then select Add trigger -> Webhook there.

<figure><img src="/files/IgTMZ9nrhE0NDuF7VvXC" alt=""><figcaption></figcaption></figure>

Then you enter the name of the webhook you are creating and any parameters if you need them. Once you have done that click save, and the webhook has been created!

<figure><img src="/files/ag6fSEvFTEKPo49HQ6CP" alt=""><figcaption></figcaption></figure>

### Running webhook

After creating the webhook, you may notice that a link has been generated that you will use to run the job from an external source.

To run the webhook send a request to this address, if you want you can also add various parameters that will appear in the body of the job but it is not required. Below is shown an example in which one parameter was sent.

```json
# Request

POST {portal address}/webhooks/triggers/{id}/run?hello=world 
# your generated webhook address goes here and should look like this

# Response

{
    "type": "job",
    "id": "1c6184bd-c49a-40bc-ac64-c2e0d84a2122",
    "workflow": {
        "id": "dbb4677b-acb9-4298-8578-ca612e24f22f",
        "name": "test_workflow-rb",
        "parameters": {
            "workflow": "test" // workflow parameters
        }
    },
    "project": {
        "id": null,
        "name": null
    },
    "trigger": {
        "id": null,
        "name": "webhook",
        "parameters": {
            "webhook": "params" // webhook parameters
        },
        "webhook": {
            "parameters": {
                "parsed_body": {
                    "body": "postman" // body sent in a request
                },
                "parsed_query": {
                    "params": "postman" // params sent in a request
                }
            }
        },
        "request_details": {
            "ip": "192.168.1.1",
            "timestamp": "2022-12-14T16:40:03.991+01:00",
            "request_method": "POST",
            "media_type": "text/plain",
            "http_headers": {
                "User-Agent": "PostmanRuntime/7.30.0",
                "Postman-Token": "5ee451ea-b1a7-43a7-884c-d37c03b9b832",
                "Accept-Encoding": "gzip, deflate, br",
                "Accept": "*/*",
                "Host": "sandbox.clients.anyrobot.cloud",
                "Version": "HTTP/1.1"
            },
            "original_url": "https://sandbox.clients.anyrobot.cloud/webhooks/triggers/33b53c6d-f0d2-4f37-b034-41f5ae999734/run?test=postman",
            "form_data": false,
            "raw_post": "{\r\n    \"body\": \"postman\"\r\n}",
            "server": "sandbox.clients.anyrobot.cloud",
            "all_headers": {
                "REQUEST_URI": "/webhooks/triggers/33b53c6d-f0d2-4f37-b034-41f5ae999734/run?test=postman",
                "PATH_INFO": "/webhooks/triggers/33b53c6d-f0d2-4f37-b034-41f5ae999734/run",
                "SCRIPT_NAME": "",
                "QUERY_STRING": "test=postman",
                "REQUEST_METHOD": "POST",
                "REMOTE_ADDR": "192.168.1.1",
                "REMOTE_PORT": "14641",
                "CONTENT_TYPE": "text/plain",
                "CONTENT_LENGTH": "27",
                "HTTPS": "on",
                "HTTP_USER_AGENT": "PostmanRuntime/7.30.0",
                "HTTP_POSTMAN_TOKEN": "5ee451ea-b1a7-43a7-884c-d37c03b9b832",
                "HTTP_ACCEPT_ENCODING": "gzip, deflate, br",
                "HTTP_ACCEPT": "*/*",
                "HTTP_HOST": "sandbox.clients.anyrobot.cloud",
                "HTTP_VERSION": "HTTP/1.1",
                "ORIGINAL_FULLPATH": "/webhooks/triggers/33b53c6d-f0d2-4f37-b034-41f5ae999734/run?test=postman",
                "ORIGINAL_SCRIPT_NAME": "",
                "RAW_POST_DATA": "{\r\n    \"body\": \"postman\"\r\n}"
            }
        }
    },
    "results_url": "https://sandbox.clients.anyrobot.cloud/api/v1/jobs/1c6184bd-c49a-40bc-ac64-c2e0d84a2122/results",
    "live_logging_url": "https://sandbox.clients.anyrobot.cloud/api/v1/jobs/1c6184bd-c49a-40bc-ac64-c2e0d84a2122/output",
    "assist_requests_url": "https://sandbox.clients.anyrobot.cloud/api/v1/jobs/1c6184bd-c49a-40bc-ac64-c2e0d84a2122/assist_requests"
}
```

### Handling webhook

To get to the parameters sent in the webhook you need to get yourself the data of the whole job, which you can get to by the environment variable (`ANYROBOT_PAYLOAD_FILE)` that points the path to the payload file. Feel free to check the documentation related to [runner environment variables](/developing-robots.md#environment-variables) to learn more about this topic.

Below is a small example of how to access the payload and what a sample payload looks like with the parameters sent in the webhook:

{% code lineNumbers="true" %}

```ruby
#!/usr/bin/env ruby
require 'json'
require 'ostruct'

file_path = ENV["ANYROBOT_PAYLOAD_FILE"]
file = File.open (file_path)
json = OpenStruct.new(JSON.parse(file.read))
```

{% endcode %}

{% code title="" lineNumbers="true" %}

```json
{
    "parameters": "{\"webhook\":\"params\"}",
    "job_id": "e3529122-4d79-426b-a652-d2b73060e897",
    "workflow_id": "dbb4677b-acb9-4298-8578-ca612e24f22f",
    "workflow_name": "test_workflow-rb",
    "trigger_name": null,
    "trigger_id": null,
    "secret": "69bbc6d2-61b3-475c-8d38-49d1c02c2008",
    "id": "e3529122-4d79-426b-a652-d2b73060e897",
    "workflow": {
        "id": "dbb4677b-acb9-4298-8578-ca612e24f22f",
        "name": "test_workflow-rb",
        "parameters": {
            "workflow": "test" // workflow parameters
        }
    },
    "trigger": {
        "id": null,
        "name": null,
        "parameters": {
            "webhook": "params" // webhook parameters
        },
        "webhook": {
            "parameters": {
                "parsed_body": {
                    "body": "postman" // data sent in body
                },
                "parsed_query": {
                    "test": "postman" // data sent as params
                }
            }
        },
        "request_details": {
            "ip": "192.168.1.1",
            "server": "sandbox.clients.anyrobot.cloud",
            "raw_post": "{\r\n    \"body\": \"postman\"\r\n}",
            "form_data": false,
            "timestamp": "2022-12-14T17:32:47.004+01:00",
            "media_type": "text/plain",
            "all_headers": {
                ...
            },
            "http_headers": {
                ...
            },
            "original_url": "https://sandbox.clients.anyrobot.cloud/webhooks/triggers/33b53c6d-f0d2-4f37-b034-41f5ae999734/run?test=postman",
            "request_method": "POST"
        }
    },
    "bot": {
        "id": "ad48bdf8-79d5-484d-8949-9d4f8f61e99a",
        "name": "Windows lukasz.b",
        "secret": "69bbc6d2-61b3-475c-8d38-49d1c02c2008"
    },
    "project": {
        "id": null,
        "name": null
    },
    "task": {
        "method": "download",
        "task_name": null,
        "branch_name": null,
        "download_url": "https://sandbox.clients.anyrobot.cloud/api/v1/jobs/e3529122-4d79-426b-a652-d2b73060e897/script"
    },
    "results_url": "https://sandbox.clients.anyrobot.cloud/api/v1/jobs/e3529122-4d79-426b-a652-d2b73060e897/results",
    "live_logging_url": "https://sandbox.clients.anyrobot.cloud/api/v1/jobs/e3529122-4d79-426b-a652-d2b73060e897/output",
    "assist_requests_url": "https://sandbox.clients.anyrobot.cloud/api/v1/jobs/e3529122-4d79-426b-a652-d2b73060e897/assist_requests"
}
```

{% endcode %}

## Attachments

Attachments are elements that mainly appear in Job Results and Assist Requests.

They come in several types, but the fields are always similar:

* **genre** - a type of attachment.
* **name** - the name of the attachment.
* **code** - code that is url-friendly - used to group the same attachments in the `electrical_parameters_voltage` series
* **body** - the body of the attachment, varies depending on the type

Currently available are the types listed below.

### Text

Text object, displayed in an iframe in Portal.

```json
{
  "genre": "text",
  "attachment_id": "123e4567-e89b-12d3-a456-426614174000",
  "name": "Lorem Ipsum",
  "code": "electrical_parameters_voltage",
  "body": "Lorem ipsum dolor amet..."
}
```

<div align="left"><figure><img src="/files/5gnFnzhbbUaPISzl3atw" alt=""><figcaption></figcaption></figure></div>

### HTML

HTML object, displayed in an iframe in Portal.

```json
{
  "genre": "html",
  "attachment_id": "123e4567-e89b-12d3-a456-426614174000",
  "name": "test.html",
  "code": "electrical_parameters_voltage",
  "body": "<p>ok</p>"
}
```

<div align="left"><figure><img src="/files/jlKDAwUPNjgAS4lNB5hj" alt=""><figcaption></figcaption></figure></div>

### File

File, of any type. The body field is encoded according to [RFC2397](http://tools.ietf.org/html/rfc2397), and has a format like this:

`data:<content-type>;base65,<base64 encoded content>`

<pre class="language-json"><code class="lang-json">{
  "genre": "file",
  "attachment_id": "123e4567-e89b-12d3-a456-426614174000",
  "name": "list.png",
<strong>  "code": "electrical_parameters_voltage",
</strong>  "body": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="
}
</code></pre>

<div align="left"><figure><img src="/files/RJyseFqOCBQrhjiMaM8r" alt=""><figcaption></figcaption></figure></div>

### Table

Data in the table is encoded in base64 format, similar to the example above, the uploaded file is a CSV file, and we also support markdowns.

```json
{
  "genre": "table",
  "attachment_id": "123e4567-e89b-12d3-a456-426614174000",
  "name": "xls.csv",
  "code": "electrical_parameters_voltage",
  "body": "a,b,c;1,2,3" //"text/csv;base64,(...)"
}
```

<div align="left"><figure><img src="/files/jSbL7kLVMMvz3Z5t6oup" alt=""><figcaption></figcaption></figure></div>

### KPI

Your key performance indicator.

```json
{
  "genre": "kpi",
  "attachment_id": "123e4567-e89b-12d3-a456-426614174000",
  "name": "stats.kpi",
  "code": "electrical_parameters_voltage",
  "body": {
    "genre": "integer",
    "value": "123",
    "target": "1000"
  }
}
```

<div align="left"><figure><img src="/files/xNVrWVKjwPEW2JGM0y5U" alt=""><figcaption></figcaption></figure></div>

## Job results

It is a request to change the status of a job.

Their structure is as follows:

* **genre** - `success` or `failure` is sent at the end of the process, `update` while the script is running.
* **code** - here Linux-standard - the error code returned by the script, 0 when success, anything over when not.
* **result** - what the script threw out.
* **attachments** - according to the documentation here [#attachments](#attachments "mention").

Currently, we can change the work status to 3 different types, and these are:

### Update

Such information comes out from the bot during Job execution, can come down many times and we display it in the user panel.

<figure><img src="/files/fW7IlpZoCrUVlyIjOfQO" alt=""><figcaption></figcaption></figure>

```json
# Request

POST /api/v1/jobs/94916326-9344-43f5-9187-1aea2931eac7/results

# Response

{
  "genre": "update",
  "result": {
    "lorem": "ipsum",
    "hello": "world"
  }, // result object, any json
  "attachments": [
    (...) // check the Attachment docs
  ]
}
```

### Success

```json
# Request

POST /api/v1/jobs/94916326-9344-43f5-9187-1aea2931eac7/results

# Response

{
  "genre": "success",
  "code": 0, // code (shell) returned by the process
  "result": {
    "lorem": "ipsum",
    "hello": "world"
  }, // result object, any json
  "attachments": [
    (...) // check the Attachment docs
  ]
}
```

### Failure

```json
# Request

POST /api/v1/jobs/94916326-9344-43f5-9187-1aea2931eac7/results

# Response

{
  "genre": "failure",
  "code": 127, // code (shell) returned by the process
  "result": {
    "lorem": "ipsum",
    "hello": "world"
  }, // result object, any json
  "attachments": [
    (...) // check the Attachment docs
  ]
}
```

## Assist Request

In some advanced cases, human intervention may be needed, and for these cases, we have this request that allows you to ask several types of questions.

The basic request should look like this, the different types will be described below.

```json
# Request

POST /api/v1/jobs/123e4567-e89b-12d3-a456-426614174000/assist_requests

{
  "expires_at": "YYYY-MM-DD HH:MM:SS", // until when the request will be valid
  "question": "Is it assisted already?", // question content
  "attachments": [],
  "genre": "input", // type of assist request
  (...) // here are the other parameters of the assist that differs by type
}

# Response
// Created
{
  "status": "201 Created",
  "id": "63be582f-d330-44ec-943b-8179e1b28c9a",
  "url": "https://serwer.xxx.yyy.com/api/v1/assist_requests/d7dd76a0-b7a5-44d7-b98f-8712aa0e5505"
}

// Not responded yet
{
  "status": "200 OK",
  "time_left": 213213214 // in seconds or null when undefined - the amount of time left for the operator to respond
}

// Time Expired
{
  "status": "410 Gone",
  "time_left": 0
}

// Responded
{
  "id": "xxxxxx-xxxxxx-xxxxx-xxxx", // response id
  "responded_at": "YYYY-MM-DD HH:MM:SS", // when responded
  // data of the user who made the reply
  "user": {
    "id": 123,
    "email": "jan.kowalski@unitedideas.pl",
    "full_name": "Jan Kowalski"
  },
  // response data that differs by type
  (...)
}
```

### Text input

```json
{
  (...) // standard request part
  "genre": "input", // request type
  "required": true, // is it required
  "confirm": true, // is it necessary to confirm the selection in the interface?
  "short": true, // when true then input, when false then textarea
  "name": "login", // field name, reference for future
  "default": "blablabla" // pre-populate interface with this answer, non-mandatory
  "validate": "/\A[^.]+\.[^.]+\Z/" # valid javascript regex to validate answer
  "error": "Login may contain only letters and saves" # validation alert if not matches
}

# Response

{
  (...) // standard response part
  "response": "loremipsum324"
}
```

<figure><img src="/files/JAtdRkbvD7NszZKzf8f5" alt=""><figcaption></figcaption></figure>

### Yes/No

```json
{
  (...) // standard request part
  "genre": "yes_or_not", // request type
  "required": true, // is it required
  "confirm": true, // is it necessary to confirm the selection in the interface?
  "default": true // not mandatory
  "positive": "Yup", // positive value
  "negative": "Nope", // negative value
}

# Response

{
  (...) // standard response part
  "response": true // or false
}
```

<figure><img src="/files/6Q1DhMha0NeK6h0ZtUQF" alt=""><figcaption></figcaption></figure>

### Radio buttons

```json
{
  (...) // standard request part
  "genre": "combo_boxes", // request type
  "required": true, // is it required
  "confirm": true, // is it necessary to confirm the selection in the interface?
  "default": 1, // not mandatory
  // possible choices
  "choices": {
    "foo": 1,
    "bar": true,
    "jA si o 🇹🇩": "pl"
  }
}

# Response

{
  (...) // standard response part
  "response": 1 // either "pl" or true
}
```

<figure><img src="/files/gGsYTXHNjBxTwHzmGu8O" alt=""><figcaption></figcaption></figure>

### Checkboxes

```json
{
  (...) // standard request part
  "genre": "checkboxes", // request type
  "required": true, // is it required
  "confirm": true, // is it necessary to confirm the selection in the interface?
  "default": 1, // not mandatory
  // possible choices
  "choices": {
    "foo": 1,
    "bar": true,
    "jA si o 🇹🇩": "pl"
  }
}

# Response

{
  (...) // standard response part
  "response": [1, true, "pl"] // all answers have been selected
}
```

<figure><img src="/files/b0rzUMa0kbYqpEvDDgVJ" alt=""><figcaption></figcaption></figure>

### List

```json
{
  (...) // standard request part
  "genre": "select", // request type
  "required": true, // is it required
  "confirm": true, // is it necessary to confirm the selection in the interface?
  "default": 1, // not mandatory
  "multi_select": true // can you select more than 1 choice?
  // possible choices
  "choices": {
    "foo": 1,
    "bar": true,
    "jA si o 🇹🇩": "pl"
  }
}

# Response

{
  (...) // standard response part
  "response": [1, true, "pl"] // all answers have been selected
}
```

<figure><img src="/files/zRCdgGcEM1qnLZfLZ0b5" alt=""><figcaption></figcaption></figure>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.anyrobot.com/portal/portal-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
