REST API guide¶
Introduction¶
The pSeven Enterprise REST API is a protocol to run and operate existing workflows or AppsHub apps.
REST API versions
pSeven Enterprise v2022.06 and newer use the updated REST API v2 described in this guide. Previous REST API version (v1) is supported for compatibility only and is no longer documented.
The API base URLs are:
{sign-in URL}/pseven/.rest/v2/
- workflow API{sign-in URL}/appshub/.rest/v2/
- app API
The {sign-in URL}
is the one you open to sign in to pSeven Enterprise with a
web browser - for example, https://app.pseven.io
.
Using the workflow or app API requires an authorization token, which must be
added to the Authorization
header in API requests - see
API access further.
The API provides read-only access to workflows and apps: since many REST clients can work with the same workflow or app at the same time, none of them should be allowed to edit that workflow or app, to avoid conflicts. To change workflow or app inputs, a REST client has to deploy a new workflow or app run first, then edit the settings of that run.
The following sections guide you through a typical run deployment and execution scenario, providing API usage examples for Python and the command line.
- Python examples found in this guide require
Requests
(
import requests
). - Command-line examples require curl and jq.
- All example code is simplified for illustration purposes, do not copy it as is.
The examples assume that you have the API base URLs, the authorization token and header stored in variables:
# Replace 'https://app.pseven.io' below with your actual sign-in URL.
api_url = 'https://app.pseven.io/pseven/.rest/v2/{}'
api_url_appshub = 'https://app.pseven.io/appshub/.rest/v2/{}'
# Replace the example value with your actual token.
auth_token = '7a592349be99d8affc6739c1ff8fec98030ff14f'
auth_header = {'Authorization': 'Token ' + auth_token}
# Replace 'https://app.pseven.io' here with your actual sign-in URL.
$ export DA__P7__REST_BASEURL="https://app.pseven.io/pseven/.rest/v2/"
$ export DA__P7__REST_BASEURL_APPSHUB="https://app.pseven.io/appshub/.rest/v2/"
# Replace the value here with your actual token.
$ export DA__P7__REST_AUTHTOKEN="7a592349be99d8affc6739c1ff8fec98030ff14f"
$ export DA__P7__REST_AUTHHEADER="Authorization: Token ${DA__P7__REST_AUTHTOKEN}"
Note that the trailing slash /
is required in endpoint URLs, as
shown in the example requests.
API access¶
API access requires an authorization token, which must be added to the
Authorization
header in API requests. You can get the token from your user
menu in pSeven Enterprise Studio, or get it through the API with a username
and a password.
To get the token in pSeven Enterprise Studio:
- Click your user icon on the upper right to open the user menu.
- In the user menu, click API token.
- Copy the token from the dialog box that appears.
To get the token through the API, send a POST request to auth/login/
with
the username and the password as parameters in the request body:
# Get an authorization token.
endpoint = 'auth/login/'
response = requests.post(
api_url.format(endpoint),
headers={'Content-Type': 'application/json'},
data='{"username": "user@work.org", "password": "secret"}'
)
auth_token = response.json()['token']
Response data:
{
"token": "7a592349be99d8affc6739c1ff8fec98030ff14f",
}
# Get an authorization token.
$ curl \
--request POST \
${DA__P7__REST_BASEURL}auth/login/ \
-H "Content-Type: application/json" \
--data '{"username": "user@work.org", "password": "secret"}' \
| jq .token
Output:
"7a592349be99d8affc6739c1ff8fec98030ff14f"
Run setup¶
To set up a workflow run, you will need to get the ID of the workflow you are going to launch, deploy its new run, and set run parameters.
The examples below explain how to get the workflow ID through the API, but you can also get the ID from pSeven Enterprise Studio:
- Select the workflow in the Explorer pane.
- Use the Copy REST API URL command from the Explorer menu.
The above command copies the workflow URL with the workflow ID to the
clipboard. For example, in the URL below a14631d477134c46a10518bc36e8e2cb
is
the workflow ID:
https://app.pseven.io/pseven/.rest/v2/workflows/a14631d477134c46a10518bc36e8e2cb/
Workflow information¶
To get a list of workflows, send a GET request to workflows/
.
The response will list all existing workflows with their names and IDs.
# Get a list of workflows.
endpoint = 'workflows/'
response = requests.get(
api_url.format(endpoint),
headers=auth_header
)
workflows = response.json()
Response data:
[
{
"id": "130d943e100e46e0b5da490ac005b70e",
"name": "ToyWorkflow",
"path": "/Users/1/ToyWorkflow.p7wf",
"url": "https://app.pseven.io/pseven/.rest/v2/workflows/130d943e100e46e0b5da490ac005b70e/"
}
{
"id": "a5017c372a76421a8ba41f91788f1b60",
"name": "AnotherToyWorkflow",
"path": "/Users/1/AnotherToyWorkflow.p7wf",
"url": "https://app.pseven.io/pseven/.rest/v2/workflows/a5017c372a76421a8ba41f91788f1b60/"
}
]
# Get a list of workflows.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
${DA__P7__REST_BASEURL}workflows/ \
| jq .
Output:
[
{
"id": "130d943e100e46e0b5da490ac005b70e",
"name": "ToyWorkflow",
"path": "/Users/1/ToyWorkflow.p7wf",
"url": "https://app.pseven.io/pseven/.rest/v2/workflows/130d943e100e46e0b5da490ac005b70e/"
}
{
"id": "a5017c372a76421a8ba41f91788f1b60",
"name": "AnotherToyWorkflow",
"path": "/Users/1/AnotherToyWorkflow.p7wf",
"url": "https://app.pseven.io/pseven/.rest/v2/workflows/a5017c372a76421a8ba41f91788f1b60/"
}
]
You can get more detailed information for a specific workflow by sending
a GET request to workflows/{workflow ID}/
.
A response to this request contains, in particular, a list of all runs for the
workflow under {workflow ID}
. Another way to get a list of runs is to
send a GET request to workflows/{workflow ID}/runs/
.
toy_wf = workflows[0]
# Get a list of workflow runs from workflow details.
endpoint = 'workflows/{}/'.format(toy_wf['id'])
response = requests.get(
api_url.format(endpoint),
headers=auth_header
)
workflow_runs = response.json()['runs']
# Get a list of workflow runs.
endpoint = 'workflows/{}/runs/'.format(toy_wf['id'])
response = requests.get(
api_url.format(endpoint),
headers=auth_header
)
workflow_runs = response.json()
Response data:
[
{
"name": "#1 (ToyWorkflow)",
"path": "/Users/1/ToyWorkflow.p7wf/@Runs/#00000001.p7run",
"id": "719572a9568d4ef0a2076b73ac536524",
"url": "https://app.pseven.io/pseven/.rest/v2/workflows/130d943e100e46e0b5da490ac005b70e/runs/719572a9568d4ef0a2076b73ac536524/",
"state": "FINISHED",
"workflow": "/Users/1/ToyWorkflow.p7wf",
"workflow_url": "https://app.pseven.io/pseven/.rest/v2/workflows/130d943e100e46e0b5da490ac005b70e/"
},
{
"name": "#2 (ToyWorkflow)",
"path": "/Users/1/ToyWorkflow.p7wf/@Runs/#00000002.p7run",
"id": "9ce34c088ded42a18e1af16353060340",
"url": "https://app.pseven.io/pseven/.rest/v2/workflows/130d943e100e46e0b5da490ac005b70e/runs/9ce34c088ded42a18e1af16353060340/",
"state": "CONFIGURATION",
"workflow": "/Users/1/ToyWorkflow.p7wf",
"workflow_url": "https://app.pseven.io/pseven/.rest/v2/workflows/130d943e100e46e0b5da490ac005b70e/"
}
]
# Get workflow details and show its runs only.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/ \
| jq .runs
# Get a list of workflow runs.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ \
| jq .
Output:
[
{
"name": "#1 (ToyWorkflow)",
"path": "/Users/1/ToyWorkflow.p7wf/@Runs/#00000001.p7run",
"id": "719572a9568d4ef0a2076b73ac536524",
"url": "https://app.pseven.io/pseven/.rest/v2/workflows/130d943e100e46e0b5da490ac005b70e/runs/719572a9568d4ef0a2076b73ac536524/",
"state": "FINISHED",
"workflow": "/Users/1/ToyWorkflow.p7wf",
"workflow_url": "https://app.pseven.io/pseven/.rest/v2/workflows/130d943e100e46e0b5da490ac005b70e/"
},
{
"name": "#2 (ToyWorkflow)",
"path": "/Users/1/ToyWorkflow.p7wf/@Runs/#00000002.p7run",
"id": "9ce34c088ded42a18e1af16353060340",
"url": "https://app.pseven.io/pseven/.rest/v2/workflows/130d943e100e46e0b5da490ac005b70e/runs/9ce34c088ded42a18e1af16353060340/",
"state": "CONFIGURATION",
"workflow": "/Users/1/ToyWorkflow.p7wf",
"workflow_url": "https://app.pseven.io/pseven/.rest/v2/workflows/130d943e100e46e0b5da490ac005b70e/"
}
]
The second run in the example above is in CONFIGURATION
state, which
means it is already deployed and waiting for launch. This is the only
state which allows changing run parameters, uploading files, and
starting the run.
Run deployment¶
To deploy a new run, send a POST request to workflows/{workflow ID}/runs/
.
The request body may specify the optional name
and path
parameters.
A request without parameters deploys a new run to the @Runs
subdirectory in the workflow. In this case, the run gets a default,
automatically generated name.
# Deploy a new run with a default name and location.
headers = {'Content-Type': 'application/json'}
headers.update(auth_header)
endpoint = 'workflows/{}/runs/'.format(toy_wf['id'])
response = requests.post(
api_url.format(endpoint),
headers=headers
)
toy_run = response.json()
Response:
201 Created
Response data:
{
"name": "#3 (ToyWorkflow)",
"path": "/Users/1/ToyWorkflow.p7wf/@Runs/#00000003.p7run",
"id": "ea51478af1c94fcea879d1bdd63faadc",
"url": "https://app.pseven.io/pseven/.rest/v2/workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/",
"state": "QUEUED_TO_INITIALIZE",
"workflow": "/Users/1/ToyWorkflow.p7wf",
"workflow_url": "https://app.pseven.io/pseven/.rest/v2/workflows/130d943e100e46e0b5da490ac005b70e/"
}
# Deploy a new run with a default name and location.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
--request POST \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ \
-H "Content-Type: application/json" | jq .
Output:
{
"name": "#3 (ToyWorkflow)",
"path": "/Users/1/ToyWorkflow.p7wf/@Runs/#00000003.p7run",
"id": "ea51478af1c94fcea879d1bdd63faadc",
"url": "https://app.pseven.io/pseven/.rest/v2/workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/",
"state": "QUEUED_TO_INITIALIZE",
"workflow": "/Users/1/ToyWorkflow.p7wf",
"workflow_url": "https://app.pseven.io/pseven/.rest/v2/workflows/130d943e100e46e0b5da490ac005b70e/"
}
You can deploy a named run: specify name
and omit path
. In this case,
the run is also deployed to @Runs
but has a custom name.
To deploy a new run to a different directory, specify the absolute path
to that directory as path
. In this case, name
is also required. A
request with the name
and path
parameters in its body creates a new run in
the name
subdirectory under path
.
# Deploy a new named run into the specified directory.
run_name = 'Toy named run'
deploy_to = 'Toy runs'
user_id = 1
headers = {'Content-Type': 'application/json'}
headers.update(auth_header)
endpoint = 'workflows/{}/runs/'.format(toy_wf['id'])
body = '{{"path": "/Users/{}/{}", "name": "{}"}}'.format(user_id, deploy_to, run_name)
response = requests.post(
api_url.format(endpoint),
headers=headers,
data=body
)
toy_named_run = response.json()
Response data:
{
"name": "Toy named run",
"path": "/Users/1/Toy runs/Toy named run.p7run",
"id": "32e0f692d0e945e5b6bbbfaa7d2ff308",
"url": "https://app.pseven.io/pseven/.rest/v2/workflows/130d943e100e46e0b5da490ac005b70e/runs/32e0f692d0e945e5b6bbbfaa7d2ff308/",
"state": "QUEUED_TO_INITIALIZE",
"workflow": "/Users/1/ToyWorkflow.p7wf",
"workflow_url": "https://app.pseven.io/pseven/.rest/v2/workflows/130d943e100e46e0b5da490ac005b70e/"
}
# Deploy a new named run into the specified directory.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
--request POST \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ \
-H "Content-Type: application/json" \
--data '{"path": "/Users/1/Toy runs", "name": "Toy named run"}' | jq .
Output:
{
"name": "Toy named run",
"path": "/Users/1/Toy runs/Toy named run.p7run",
"id": "32e0f692d0e945e5b6bbbfaa7d2ff308",
"url": "https://app.pseven.io/pseven/.rest/v2/workflows/130d943e100e46e0b5da490ac005b70e/runs/32e0f692d0e945e5b6bbbfaa7d2ff308/",
"state": "QUEUED_TO_INITIALIZE",
"workflow": "/Users/1/ToyWorkflow.p7wf",
"workflow_url": "https://app.pseven.io/pseven/.rest/v2/workflows/130d943e100e46e0b5da490ac005b70e/"
}
The response to the run deployment request will contain the run ID and a
few other details. Run state will be QUEUED_TO_INITIALIZE
, which means
it has just entered the deployment queue and is not ready for configuring yet.
Before changing the run parameters, get the run details by sending a GET
request to workflows/{workflow ID}/runs/{run ID}/
. Verify that the run state
is CONFIGURATION
, otherwise your run setup requests will be rejected.
# Check run state.
endpoint = 'workflows/{}/runs/{}/'.format(toy_wf['id'], toy_run['id'])
response = requests.get(
api_url.format(endpoint),
headers=auth_header
)
run_state = response.json()['state']
Run state in the response data should be:
'CONFIGURATION'
# Check run state.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/ \
| jq .state
Output should be:
"CONFIGURATION"
Run parameters¶
Change the run parameters by sending a PATCH request to
workflows/{workflow ID}/runs/{run ID}/
.
The request body should contain run details with new parameter values.
The response body will contain updated run details.
Parameters are an array in run details (.parameters
). Each parameter
has a unique identifier (.parameters[{index}].id
). New parameter
identifiers are generated in every new run you deploy. Parameter name
(.parameters[{index}].name
) is usually the same as the name of the
port mapped to the parameter. If this name is not unique in the workflow,
it is prefixed with the block name; if the prefixed name is also not
unique, it is additionally prefixed with the name of the parent
Composite block, and so on. For example:
- "MyFirstParameter" and "MySecondParameter" are names of parameters
mapped to the input ports of some block or two different blocks. In
this case the ports are named
MyFirstParameter
andMySecondParameter
, respectively. - "OneBlock/MyParameter" and "TwoBlock/MyParameter" are names of parameters mapped to input ports of two different blocks. In this case, ports have the same name, so parameter names are prefixed with block names.
- "Composite 1/MyBlock/MyParameter" and "Composite 2/MyBlock/MyParameter" are names of parameters mapped to input ports of two different blocks nested in two different Composite block. In this case, port and block names are the same, so parameter names include the names of Composite blocks.
It is recommended to identify parameters by id
, and to use name
as
a display name only, if you develop a REST client with a GUI.
Parameter value is found in .parameters[{index}].value.data
; other keys in
.parameters[{index}].value
provide metainformation for the current value.
Extra information about the parameter may be available from its
labels
(.parameters[{index}].labels
).
# Get run parameter IDs, names, values, and labels.
endpoint = 'workflows/{}/runs/{}/'.format(toy_wf['id'], toy_run['id'])
response = requests.get(
api_url.format(endpoint),
headers=auth_header
)
run_params = response.json()['parameters']
run_params_values = {p['id']: {'name': p['name'], 'value': p['value']['data'], 'labels': p['labels']} for p in run_params}
The run_params_values
dictionary should be similar to:
{
'fd10fa8bafb642f29ca89542504970ae':
{
'name': 'MyFirstParameter',
'value': 'string_value',
'labels': {'type': 'string'}
},
'db4bc72e7a594ba9b0a7009d95013648':
{
'name': 'MySecondParameter',
'value': 42,
'labels': {'type': 'integer, real, string'}
},
'ba7236e82c9b17f242a91ec87d2a5e2d':
{
'name': 'MySecret',
'value': None,
'labels': {'note': 'in the REST API, the real value is hidden'}
}
# ...
}
# Get run parameter IDs, names, values, and labels.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/ \
| jq "[.parameters[] | {id: .id, name: .name, value: .value.data, labels: .labels}]"
Output should look like:
[
{
"id": "fd10fa8bafb642f29ca89542504970ae",
"name": "MyFirstParameter",
"value": "string_value",
"labels": {"type": "string"}
},
{
"id": "db4bc72e7a594ba9b0a7009d95013648",
"name": "MySecondParameter",
"value": 42,
"labels": {"type": "integer, real, string"}
},
{
"id": "ba7236e82c9b17f242a91ec87d2a5e2d",
"name": "MySecret",
"value": null,
"labels": {"note": "the real value is a secret"}
}
]
Note the parameter named MySecret
in the examples, which illustrates the Secret data type.
API requests do not return the values of such parameters that are actually set in your run.
Instead, you get
null
(in Python, None
) if the parameter is unset, as in the example above, and
an empty string if some value is set, as in the example below.
# Get run details.
endpoint = 'workflows/{}/runs/{}/'.format(toy_wf['id'], toy_run['id'])
response = requests.get(
api_url.format(endpoint),
headers=auth_header
)
run_details = response.json()
# Change parameter values.
run_params = run_details['parameters']
run_params_values = {
p['id']: {
'name': p['name'],
'value': p['value']['data']
} for p in run_params}
run_params_values[{parameter ID}]['value'] = 'new_string_value'
run_params_values[{parameter ID}]['value'] = 43
# Secret values are set in the same way as others.
run_params_values[{parameter ID}]['value'] = 'new_access_token'
for p in run_details['parameters']:
p['value']['data'] = run_params_values[p['id']]['value']
# Send changes and check new parameter values to verify the run setup.
headers = {'Content-Type': 'application/json'}
headers.update(auth_header)
endpoint = 'workflows/{}/runs/{}/'.format(toy_wf['id'], toy_run['id'])
response = requests.patch(
api_url.format(endpoint),
headers=headers,
json=run_details
)
new_params = response.json()['parameters']
new_params_values = {p['id']: {'name': p['name'], 'value': p['value']['data']} for p in run_params}
The response to the request that patches parameters
(the new_params_values
dictionary above)
should contain the same parameter values as those sent with the request -
except the values of Secret type, which in the response will be replaced by empty strings,
only indicating that some value for a secret parameter is now set.
{
'fd10fa8bafb642f29ca89542504970ae':
{
'name': 'MyFirstParameter',
'value': 'new_string_value'
},
'db4bc72e7a594ba9b0a7009d95013648':
{
'name': 'MySecondParameter',
'value': 43
},
'ba7236e82c9b17f242a91ec87d2a5e2d':
{
'name': 'MySecret',
'value': ''
}
}
# Get run details and save them to a file.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/ \
--output run_details.json
# (Change parameter values in run_details.json, save changes).
# Send changes and check new parameter values to verify that it worked.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
-H "Content-Type: application/json" \
--request PATCH \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/ \
--data "@run_details.json" \
| jq "[.parameters[] | {id: .id, name: .name, value: .value.data}]"
Output should contain the same values as those you have specified in run_details.json
-
except the values of Secret type, which in the response will be replaced by empty strings,
only indicating that some value for a secret parameter is now set.
[
{
"id": "fd10fa8bafb642f29ca89542504970ae",
"name": "MyFirstParameter",
"value": "new_string_value"
},
{
"id": "db4bc72e7a594ba9b0a7009d95013648",
"name": "MySecondParameter",
"value": 43
},
{
"id": "ba7236e82c9b17f242a91ec87d2a5e2d",
"name": "MySecret",
"value": ""
}
]
Run commands¶
The following API endpoints provide the run commands:
workflows/{workflow ID}/runs/{run ID}/run/
- the launch commandworkflows/{workflow ID}/runs/{run ID}/interrupt/
- the break command
A POST request to workflows/{workflow ID}/runs/{run ID}/run/
starts the run. Run state should be CONFIGURATION
. Upon receiving this
request, run state immediately changes to QUEUED_TO_RUN
, which means
it is placed in the run queue but is not launched yet.
# Check run state.
endpoint = 'workflows/{}/runs/{}/'.format(toy_wf['id'], toy_run['id'])
response = requests.get(
api_url.format(endpoint),
headers=auth_header
)
run_state = response.json()['state']
# Launch the run.
endpoint = 'workflows/{}/runs/{}/run/'.format(toy_wf['id'], toy_run['id'])
if run_state == 'CONFIGURATION':
response = requests.post(
api_url.format(endpoint),
headers=auth_header
)
Response data:
{
"status": 200,
"message": "Accepted"
}
# Check run state.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/ \
| jq .state # Should output "CONFIGURATION".
# Launch the run.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
--request POST \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/run/ \
| jq .
Output:
{
"status": 200,
"message": "Accepted"
}
Once the run starts, its state changes to RUNNING
. In this state you
can exchange data with blocks in the workflow (see
Messaging). Once the run completes, its state changes to
FINISHED
on success, or FAILED
on error.
If you need to interrupt a run sooner than it finishes normally, send a POST
request to workflows/{workflow ID}/runs/{run ID}/interrupt/
. Run state must
be RUNNING
, and once the run stops, it changes to INTERRUPTED
.
# Interrupt a run.
endpoint = 'workflows/{}/runs/{}/interrupt/'.format(toy_wf['id'], toy_run['id'])
response = requests.post(
api_url.format(endpoint),
headers=auth_header
)
Response data:
{
"status": 200,
"message": "Accepted"
}
# Interrupt a run.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
--request POST \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/interrupt/ \
| jq .
Output:
{
"status": 200,
"message": "Accepted"
}
Run results¶
After a run finishes, you can get the values of the workflow result ports.
The result values are available when the run state is FINISHED
.
# Check run state.
endpoint = 'workflows/{}/runs/{}/'.format(toy_wf['id'], toy_run['id'])
response = requests.get(
api_url.format(endpoint),
headers=auth_header
)
run_state = response.json()['state']
Run state in the response data should be:
'FINISHED'
# Check run state.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/ \
| jq .state
Output should be:
"FINISHED"
Once the run reaches the FINISHED
state, request the run details again. They
now contain results data in the .results
array, which earlier was empty.
The .results
array structure is similar to the .parameters
array.
# Get names, values, and labels of run results from run details.
endpoint = 'workflows/{}/runs/{}/'.format(toy_wf['id'], toy_run['id'])
response = requests.get(
api_url.format(endpoint),
headers=auth_header
)
run_results = response.json()['results']
run_results_values = [{'name': r['name'], 'value': r['value']['data'], 'labels': r['labels']} for r in run_results]
The run_results_values
list might be, for example:
[
{
'name': 'MyResult',
'value': 99,
'labels': {'type': 'integer'}
},
{
'name': 'MyAdditionalResult',
'value': 'no_comments',
'labels': {'type': 'string'}
},
{
'name': 'MyFirstParameter',
'value': 'new_string_value',
'labels': {'type': 'string'}
},
{
'name': 'MySecondParameter',
'value': 43,
'labels': {'type': 'integer, real, string'}
}
]
# Get names, values, and labels of run results from run details.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/ \
| jq "[.results[] | {name: .name, value: .value.data, labels: .labels}]"
Output:
[
{
"name": "MyResult",
"value": 99,
"labels": {"type": "integer"}
},
{
"name": "MyAdditionalResult",
"value": "no_comments",
"labels": {"type": "string"}
},
{
"name": "MyFirstParameter",
"value": "new_string_value",
"labels": {"type": "string"}
},
{
"name": "MySecondParameter",
"value": 43,
"labels": {"type": "integer, real, string"}
}
]
Note that a port can be both a parameter and a result port, so for some
workflows you may find the input values of parameters in the .results
array too. Those values are logged from ports in run-time, after the run
has started. You can use them for some sanity checks - for example, to
check that the run really accepted the parameter values you had set, you
can have your REST client save the parameter values it sends, and
compare those saved values with the parameter values found in results.
Deleting runs¶
You can delete a run that has not yet started, or had already stopped:
send a DELETE request to workflows/{workflow ID}/runs/{run ID}/
.
The run state has to be
CONFIGURATION
, FINISHED
, FAILED
, or INTERRUPTED
.
If the run is in progress (RUNNING
), you will have to interrupt it first
before the deletion (see Run commands).
# Get run details, check its state, interrupt if it is running.
run_endpoint = 'workflows/{}/runs/{}/'.format(toy_wf['id'], toy_run['id'])
response = requests.get(
api_url.format(run_endpoint),
headers=auth_header
)
run_state = response.json()['state']
if run_state not in ['CONFIGURATION', 'RUNNING', 'FINISHED', 'FAILED', 'INTERRUPTED']:
# Cannot delete the run now, retry later.
raise NotImplementedError("REST client should wait until deleting the run is possible.")
if run_state == 'RUNNING':
# Interrupt the run, and delete it after its state changes to 'INTERRUPTED'.
raise NotImplementedError("REST client should interrupt the run before deleting it.")
# Request the run deletion.
response = requests.delete(
api_url.format(run_endpoint),
headers=auth_header
)
Response data:
{"status": 200}
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/ \
| jq .state
# The state must be "CONFIGURATION", "FINISHED", "FAILED", or "INTERRUPTED".
# If the state is "RUNNING", interrupt the run, then delete it in the "INTERRUPTED" state.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
--request DELETE \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/ \
| jq .
Output:
{"status": 200}
Check that the run has been deleted:
the request to get run details must return 404 Not Found
.
# Before you continue, check that the run has been deleted.
run_endpoint = 'workflows/{}/runs/{}/'.format(toy_wf['id'], toy_run['id'])
response = requests.get(
api_url.format(run_endpoint),
headers=auth_header
)
if response.status_code != 404:
# The run is being deleted, recheck later.
# You can set a timeout and assume the deletion failed, if that timeout is exceeded.
raise NotImplementedError("REST client should check that a run was deleted successfully.")
After the deletion completes, the response is:
HTTP/1.1 404 Not Found
Response data:
{"detail": "Not found."}
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/ \
-i -w "\n"
After the deletion completes, the output is:
HTTP/1.1 404 Not Found
{"detail": "Not found."}
File download¶
You can download a file located somewhere inside the run directory
(specified on start, see Run deployment) by sending a
GET request to workflows/{workflow ID}/runs/{run ID}/download/
with
the file path as the file
query string parameter. The path is relative
to the run directory.
# Get the results.json file from the run directory root.
file_path = 'results.json'
endpoint = 'workflows/{}/runs/{}/download/'.format(toy_wf['id'], toy_run['id'])
response = requests.get(
api_url.format(endpoint),
headers=auth_header,
params=(('file', file_path),),
)
with open(file_path, 'wb') as f:
f.write(response.content)
# Get the results.json file from the run directory root.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/download/?file=results.json \
--output results.json
Listing files¶
You can list files and subdirectories located inside the run directory by
sending a GET request to workflows/{workflow ID}/runs/{run ID}/ls/
.
# List files and subdirectories in the run directory.
endpoint = 'workflows/{}/runs/{}/ls/'.format(toy_wf['id'], toy_run['id'])
response = requests.get(
api_url.format(endpoint),
headers=auth_header,
)
Response data:
{
"status": 200,
"message": "OK",
"entries": [
{
"name": "Optimizer.p7table",
"size": 4421,
"modified": "2025-03-13T15:07:20.077465",
"is_dir": false
},
{
"name": "🏠.Disk Model [0001].p7wd",
"size": 2,
"modified": "2025-03-13T15:07:00.229327",
"is_dir": true
},
...
]
"count": 27
}
# List files and directories in the run directory.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/ls/?directory=%F0%9F%8F%A0.Disk%20Model%20%5B0001%5D.p7wd \
| jq .
Output:
{
"status": 200,
"message": "OK",
"entries": [
{
"name": "Optimizer.p7table",
"size": 4421,
"modified": "2025-03-13T15:07:20.077465",
"is_dir": false
},
{
"name": "🏠.Disk Model [0001].p7wd",
"size": 2,
"modified": "2025-03-13T15:07:00.229327",
"is_dir": true
},
...
],
"count": 27
}
Note that subdirectories have "is_dir": true
while files have "is_dir": false
.
To list the contents of a subdirectory, add the directory
query parameter
with the name of the subdirectory to list.
# List the directory 🏠.Disk Model [0001].p7wd in the run directory root.
response = requests.get(
api_url.format(endpoint),
headers=auth_header,
params=(("directory", "🏠.Disk Model [0001].p7wd"),),
)
Response data:
{
"status": 200,
"message": "OK",
"entries": [
{
"name": "design.csv",
"size": 576,
"modified": "2025-03-13T15:06:58.465315",
"is_dir": false
},
{
"name": "diagrams.png",
"size": 29706,
"modified": "2025-03-13T15:06:59.233320",
"is_dir": false
}
],
"count": 2
}
# List the directory 🏠.Disk Model [0001].p7wd in the run directory root.
# Directory name should be URL-encoded:
# %F0%9F%8F%A0.Disk%20Model%20%5B0001%5D.p7wd is 🏠.Disk Model [0001].p7wd.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/ls/?directory=%F0%9F%8F%A0.Disk%20Model%20%5B0001%5D.p7wd \
| jq .
Output:
{
"status": 200,
"message": "OK",
"entries": [
{
"name": "design.csv",
"size": 576,
"modified": "2025-03-13T15:06:58.465315",
"is_dir": false
},
{
"name": "diagrams.png",
"size": 29706,
"modified": "2025-03-13T15:06:59.233320",
"is_dir": false
}
],
"count": 2
}
Downloading the run log¶
If the run state is FINISHED
, a diagnostic log is available for download.
The log is a text file, which you can download by sending a GET request to
workflows/{workflow ID}/runs/{run ID}/download/
(see File download).
The log download full URL for a finished run can be found under the
logfile_url
key in run details.
# Get the log download URL from run details.
endpoint = 'workflows/{}/runs/{}/'.format(toy_wf['id'], toy_run['id'])
response = requests.get(
api_url.format(endpoint),
headers=auth_header
)
toy_run['logfile_url'] = response.json()['logfile_url']
A run log download request URL looks like:
'https://app.pseven.io/pseven/.rest/v2/workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/download/?file=.p7.log.txt'
# Get the log download URL from run details.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/ \
| jq .logfile_url
Output:
"https://app.pseven.io/pseven/.rest/v2/workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/download/?file=.p7.log.txt"
Use the obtained URL in the download request.
# Download the run log and save it as {run name}.log
response = requests.get(
toy_run['logfile_url'],
headers=auth_header
)
with open(toy_run['name'] + '.log', 'wb') as f:
f.write(response.content)
# Download the run log and save it as toy_run.log
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/download/?file=.p7.log.txt \
--output toy_run.log
File upload¶
You can use the API to upload files to the run directory (specified when setting up the workflow run, see Run deployment). It is possible to upload to the directory root as well as to subfolders in the run directory.
Files can only be uploaded if the run state is CONFIGURATION
.
# Check run state.
endpoint = 'workflows/{}/runs/{}/'.format(toy_wf['id'], toy_run['id'])
response = requests.get(
api_url.format(endpoint),
headers=auth_header
)
run_state = response.json()['state']
Run state in the response data should be:
'CONFIGURATION'
# Check run state.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/ \
| jq .state
Output should be:
"CONFIGURATION"
In a run state other than CONFIGURATION
, uploading files results in
an error.
To upload files, send a POST request with the content type
multipart/form-data
to workflows/{workflow ID}/runs/{run ID}/upload/
.
# Upload files and create folders in the run directory.
paths = ('settings.csv', 'info.txt') # The files to upload.
upload_as = ('params.csv', None) # Whether to rename files upon upload.
upload_to = 'run_setup' # The upload destination subfolder in the run directory.
create_dirs = ('init', 'preprocess') # The folders to create.
uploads = []
uploads.append(('destination', (None, upload_to)))
for path, rename in zip(paths, upload_as):
if rename:
f = ('file', (rename, open(path, 'rb')))
else:
f = ('file', open(path, 'rb'))
uploads.append(f)
for name in create_dirs:
uploads.append(('directory', (None, name)))
# The uploads list contains:
# [
# ('destination', (None, 'run_setup')),
# ('file', ('params.csv', <open file 'settings.csv'>)),
# ('file', <open file 'info.txt'>),
# ('directory', (None, 'init')),
# ('directory', (None, 'preprocess'))
# ]
endpoint = 'workflows/{}/runs/{}/upload/'.format(toy_wf['id'], toy_run['id'])
response = requests.post(
api_url.format(endpoint),
headers=auth_header,
files=uploads
)
Response data:
{
"status": 200,
"message": "OK",
"uploaded_files": [
"/Users/1/ToyWorkflow.p7wf/@Runs/#00000003.p7run/run_setup/params.csv",
"/Users/1/ToyWorkflow.p7wf/@Runs/#00000003.p7run/run_setup/info.txt"
],
"uploaded_directories": [
"/Users/1/ToyWorkflow.p7wf/@Runs/#00000003.p7run/run_setup/init",
"/Users/1/ToyWorkflow.p7wf/@Runs/#00000003.p7run/run_setup/preprocess"
]
}
# Upload files and create folders in the run directory.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
-F "destination=run_setup" \
-F "file=@settings.csv;filename=params.csv" \
-F "file=@info.txt" \
-F "directory=init" \
-F "directory=preprocess" \
${DA__P7__REST_BASEURL}workflows/130d943e100e46e0b5da490ac005b70e/runs/ea51478af1c94fcea879d1bdd63faadc/upload/ \
| jq .
Output:
{
"status": 200,
"message": "OK",
"uploaded_files": [
"/Users/1/ToyWorkflow.p7wf/@Runs/#00000003.p7run/run_setup/params.csv",
"/Users/1/ToyWorkflow.p7wf/@Runs/#00000003.p7run/run_setup/info.txt"
],
"uploaded_directories": [
"/Users/1/ToyWorkflow.p7wf/@Runs/#00000003.p7run/run_setup/init",
"/Users/1/ToyWorkflow.p7wf/@Runs/#00000003.p7run/run_setup/preprocess"
]
}
The example request above:
- Creates the
run_setup
destination folder in the run directory, if this folder does not exist - Uploads
settings.csv
to therun_setup
folder asparams.csv
; overwritesrun_setup/params.csv
if it exists - Uploads
info.txt
to therun_setup
folder; overwritesrun_setup/info.txt
if it exists - Creates the empty
init
andpreprocess
folders inrun_setup
, if those folders do not exist
The response always lists the files that were uploaded (the array under
the uploaded_files
key) and the empty folders that were created
(the array under the uploaded_directories
key). One of those arrays
may be empty.
The upload request content fields are explained below.
destination
-
The upload destination.
- Single field: you can specify only one upload destination
- Optional: if you omit this field, the files are uploaded to the run directory root
The
destination
field content is a string specifying the relative or absolute path to the upload destination in the run directory. Use a relative path to upload to a subfolder in the run directory (the destination path is relative to the run directory root). You can use an absolute path, provided that the folder under that path is in the run directory; otherwise, the upload will fail. If the folder at the specified path does not exist, it will be created. file
-
A file to upload.
- Multiple field: you can upload multiple files in a single request
- Optional: you may omit this field, if you specify at least one
directory
field - Attribute:
filename
, an optional attribute you can add to upload the file under a different name
The
file
field content is the file data to upload.Filename conflicts are ignored: if a file with the same name exists in the upload destination, the file you upload overwrites the existing one.
The optional
filename
attribute sets the name of the target file in the upload destination.The
file
field is optional, so you can use an upload request without anyfile
fields to create subfolders in the run directory. directory
-
A subfolder to create in the run directory.
- Multiple field: you can create multiple folders with a single request
- Optional: you may omit this field, if you specify at least one
file
field
The
directory
field content is a string specifying the folder name and path relative to the upload destination, or relative to the run directory root if you omit thedestination
field. If such a folder does not exist yet, the request creates an empty folder with the specified path and name.
Messaging¶
While a run is in progress, blocks can exchange messages with the REST client using one or more communication channels. A channel works as a pair of message queues:
- Channel's platform-to-client queue holds messages sent to this channel by blocks that use it. The client can dequeue messages from here to read them.
- Channel's client-to-platform queue holds messages sent to this channel by the client. Any block can dequeue and read these messages.
Channels are created on the fly when you specify a channel name in an API call. Named channels are optional: for simple tasks, you can omit the channel name parameter to use the default unnamed channel.
Block API¶
In a Python script block, import the api
module, then use
api.message_push()
and api.message_pop()
.
api.message_push(message, channel=None)
-
Send a message from the block.
- Parameters:
message
(string
) - message datachannel
(string
) - name of the channel to use
- Returns:
None
Pushes a message to the platform-to-client queue. Channel name is optional, uses the default channel if none specified.
- Parameters:
api.message_pop(channel=None, timeout=0)
-
Receive a message on the block.
- Parameters:
channel
(string
) - name of the channel to read fromtimeout
(int
) - receive timeout in seconds, 0 for none
- Returns: message (
string
) - Raises:
TimeoutError
if timeout exceeded
Pops a message from the client-to-platform queue. Channel name is optional: if not specified, reads the default channel.
- Parameters:
Note that api.message_pop()
is a blocking call: the block's script will wait
until it returns a value. By default, there is no timeout, so the block waits
for a new message indefinitely once it calls api.message_pop()
. Usually this
behavior is unwanted - for example, if the client gets disconnected, the
client-to-platform queue remains empty, so the block is never going to finish.
However, if you set a timeout, api.message_pop()
method waits the specified
number of seconds at most and raises a TimeoutError
when it exceeds the
timeout. Unless you handle that exception in your script, it stops the
workflow run when raised.
Client API¶
Clients use the run's message_push/
and message_pop/
endpoints. Both
of them accept POST requests with optional channel specification.
To send a message from the client, do a POST request to
workflows/{workflow ID}/runs/{run ID}/message_push/
.
This request pushes a message to the client-to-platform queue. Request
body should be a JSON in the following format:
{"message": "message data", "channel": "channel name"}
. The "channel"
key
is optional - if you omit it, the message is sent to the default channel.
Possible responses to the message push (send) request are:
- Message accepted:
202 Accepted
with an empty body - Error: error status code with a body containing error details (see Error Handling)
To get a message on the client, send a POST request to
workflows/{workflow ID}/runs/{run ID}/message_pop/
.
This request pops a message from the platform-to-client queue. The
request body may be a JSON specifying which channel to read:
{"channel": "channel name"}
. If the body is empty, a message is read from
the default channel.
Possible responses to the message pop (receive) request are:
- Got a message:
200 OK
with a JSON body in the{"message": "message data"}
format - No messages in queue:
204 No Content
with an empty body - Error: error status code with a body containing error details (see Error Handling)
AppsHub API¶
You can use the pSeven Enterprise REST API to publish workflows as AppsHub apps (see Workflow publishing) and to run the published apps.
The app API methods are mostly the same as those of the workflow API. Notable differences are:
- The app API base URL is
{sign-in URL}/appshub/.rest/v2/
. (theapi_url_appshub
,DA__P7__REST_BASEURL_APPSHUB
variables in examples). - The app API endpoint name prefix is
apps
. For example,apps/{app ID}/runs/
is the endpoint to get the list of app runs for that{app ID}
. - The app run deployment request has one more parameter,
personal
. By default aapp runs are hosted in the workspace where the app is published, and will use workspace resources. You can set this parameter to run the app consuming personal (user) resources. - Also, the run deployment request does not support the
path
parameter as it would have no use in AppsHub.
# Get the list of published apps.
endpoint = 'apps/'
response = requests.get(
api_url_appshub.format(endpoint),
headers=auth_header
)
apps = response.json()
toy_app = apps[0]
# Deploy a new named run, which will use personal (not workspace) resources.
settings = {
# Optional: deploys a named run if set.
'name': 'Test personal run',
# The run path parameter is not supported in AppsHub.
# 'path': '',
# False - host the run in the app's workspace, True - use personal resources.
# Optional: if omitted, uses workspace resources (same as False).
'personal': True
}
endpoint = 'apps/{}/runs/'.format(toy_app['id'])
response = requests.post(
api_url_appshub.format(endpoint),
headers=auth_header,
data=settings
)
toy_named_run = response.json()
# ...
# Get the list of published apps.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
${DA__P7__REST_BASEURL_APPSHUB}apps/ \
| jq .
# Deploy a new named run, which will use personal (not workspace) resources.
$ curl \
-X POST \
-H "${DA__P7__REST_AUTHHEADER}" \
-F "name=Test personal run" \
-F "personal=True" \
${DA__P7__REST_BASEURL_APPSHUB}apps/c53461be10b84975a4a6ecb755cfbe00/runs/ \
| jq .
# ...
You can get the app ID through the API (similar to the workflow ID), or from
AppsHub: hover the app thumbnail and click the icon
on the upper right. This copies the app URL with the app ID to the clipboard.
For example, in the URL below c53461be10b84975a4a6ecb755cfbe00
is the app
ID:
https://app.pseven.io/appshub/.rest/v2/apps/c53461be10b84975a4a6ecb755cfbe00/
Workflow publishing¶
You can use the AppsHub API to publish a workflow as an app:
send a POST request to apps/
, specifying
the workspace to host the app,
the workflow to publish,
and app settings.
The request must specify an existing workspace where you have the
"Publish apps" role
assigned by a pSeven Enterprise admin.
# Publish a workflow as an app to AppsHub.
# Get a list of workflows.
endpoint = 'workflows/'
response = requests.get(
api_url.format(endpoint),
headers=auth_header
)
workflows = response.json()
# Select a workflow and prepare to publish.
toy_wf = workflows[0]
settings = {
# The name of the workspace to host the app, required.
# Must be an existing workspace where you have the "Manage workspace" role.
'workspace.name': 'Toy apps',
# The app name, required. To update an existing app, specify its current name.
'name': 'Toy app',
# The workflow to publish, required.
'workflow_path': toy_wf['path'],
# A brief app description, optional.
'description': 'A toy app to try publishing via REST.',
# Version control, optional. Set None or omit to publish as a new version.
# Specify an existing version number to update that version. This will add a new revision to it.
'version': None,
# Designate the published version as the default one (True) or keep the current default (False).
# Optional. If omitted, the published version becomes default (the value defaults to True).
# Ignored (assumed True) if you are publishing a new app: the initial version is always default.
'set_version_as_default': False,
# Whether to allow users to copy the workflow from the app.
# Optional. If omitted, keeps the existing app setting.
# If omitted when publishing a new app, defaults to True (allow copying).
'allow_get_as_workflow': False
}
app_thumb = open('thumbnail.png', 'rb') # The app thumbnail, optional.
# Publish the workflow with the above settings and thumbnail.
endpoint = 'apps/'
response = requests.post(
# Note this request goes to the app API (api_url_appshub, /appshub/.rest).
api_url_appshub.format(endpoint),
headers=auth_header,
data=settings,
files={'thumbnail': app_thumb}
)
toy_app_v1 = response.json()
Response:
201 Created
Response data:
{
"name": "Toy app",
"path": "/Apps/Toy app.p7wf",
"id": "130d943e100e46e0b5da490ac005b70e",
"readme": null,
"url": "https://app.pseven.io/appshub/.rest/v2/apps/130d943e100e46e0b5da490ac005b70e@1/",
"presets": null,
"parameters": null,
"results": null,
"runs": [],
"ui_url": null,
"app_url": null,
"workflow": {...},
"thumbnail": null,
"fingerprint": null,
"version": 1,
"revision": 0,
"updated": "2024-02-02T10:23:19.995035Z",
"versions": [],
"workspace": {
"id": "7",
"name": "Toy apps"
}
"state": "QUEUED_TO_INITIALIZE"
}
# Get the workflow path by its index in the list of workflows.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
${DA__P7__REST_BASEURL}workflows/ \
| jq .[0].path
# Output: "/Users/1/ToyWorkflow.p7wf"
# Publish the workflow and get the app ID, URL, state, and its workspace details.
$ curl \
-X POST \
-H "${DA__P7__REST_AUTHHEADER}" \
-F "workspace.name=Toy apps" \
-F "name=Toy app" \
-F "workflow_path=/Users/1/ToyWorkflow.p7wf" \
-F "description=A toy app to try publishing via REST." \
-F "version=" \
-F "set_version_as_default=False" \
-F "allow_get_as_workflow=False" \
-F "thumbnail=@./thumbnail.png" \
${DA__P7__REST_BASEURL_APPSHUB}apps/ \
| jq .id,.url,.state,.workspace
Output:
"130d943e100e46e0b5da490ac005b70e"
"https://app.pseven.io/appshub/.rest/v2/apps/130d943e100e46e0b5da490ac005b70e@1/"
"QUEUED_TO_INITIALIZE"
{
"id": "7",
"name": "Toy apps"
}
The response to the publish request contains incomplete details of the app
version being published, including the app ID, version and revision numbers,
and the URL for that version. The QUEUED_TO_INITIALIZE
state indicates that
the publishing process is yet starting.
Before you deploy a new run (of any app version), send a GET request to that
version's URL to get its details, and check that its state is READY
. Only
deploy new runs from the READY
state, otherwise AppsHub will respond with
an error.
# Get the app version details, check its state.
response = requests.get(
toy_app_v1['url'],
headers=auth_header
)
toy_app_v1 = response.json()
if toy_app_v1['state'] == 'READY':
# This version has finished publishing, you can deploy a new run.
else:
# Publishing is still in progress, try later.
# Get the app details, check the app state.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
https://app.pseven.io/appshub/.rest/v2/apps/130d943e100e46e0b5da490ac005b70e/ \
| jq .state
Output should be:
"READY"
To publish an app with a thumbnail, the request content type must be
multipart/form-data
. If you omit the thumbnail, it may also be
application/json
or application/x-www-form-urlencoded
.
The publish request content fields are explained below.
workspace.name
-
The name of the workspace to host the app, a string.
- Single field
- Optional if you specify
workspace.id
The publish request must specify the workspace to host the app. You can specify it by name or id, at least one of these fields is required. If you specify both, they must match.
To find the name of a workspace, you can:
- Open the AppsHub main page and check app details there.
- Bring up the publish dialog in Studio and check the workspace dropdown there.
- Get details of some existing app (GET
apps/{app ID}/
), which contain the app's workspace name and ID.
workspace.id
-
The ID of the workspace to host the app, a string.
- Single field
- Optional if you specify
workspace.name
Workspace IDs are not found in AppsHub or Studio UI, though you can use ID instead of name if you already know it. Note that workspace IDs should be sent and handled as strings.
To get the workspace ID, you can:
- Ask an admin to open the Workspaces page in the admin interface
(or go to
{admin URL}/app_pseven/workspace/
). Workspace IDs can be found in the link addresses on that page - for example,{admin URL}/app_pseven/workspace/7/change/
links to a workspace settings page, the ID is"7"
. - Get details of some existing app (GET
apps/{app ID}/
), which contain the app's workspace name and ID.
name
-
The app name, a string.
- Single field
- Required
Each app has a unique name. If some app has already been published under a certain name, your request to publish an app with the same name updates that earlier-published app:
- If you do not specify any version (omit the
version
field), your publish request adds a new app version. The existing versions remain available without changes. - If you specify an existing version, your publish request creates a new revision of that version, removing its current revision. The removed revision is no longer available. This only affects the specified version; all other versions remain intact.
workflow_path
-
The path to the workflow to publish, a string.
- Single field
- Required
The full path to the workflow in the pSeven Enterprise data storage. You should get it through the API by requesting the workflow details.
description
-
The app description, a string.
- Single field
- Optional
The brief description of the app displayed in the app list on the main AppsHub page.
If you are updating an existing app and specify this field, it will overwrite the app description. If you omit this field upon update, the app keeps its current description.
version
-
The app version to update when publishing over an existing app, integer.
- Single field
- Optional
If you are publishing a new app, omit this field: the initial version number is always 1.
If you are updating an existing app (see the
name
field), you can:- Omit this field. Your update adds a new version of the existing app. The existing versions remain available without changes.
- Specify the number of an existing version. Your update creates a new revision of that version, removing its current revision. The removed revision is no longer available. This only affects the specified version; all other versions remain intact.
set_version_as_default
-
Whether to set the published (updated) version as default, Boolean.
- Single field
- Optional
An app always has one of its existing versions designated as the default one. This is not necessarily the most recent version - for example, your app might have 4 versions (v1-v4) but designate v3 as the default one. When updating an existing app, you can keep or change this designation:
- To keep the current default, set
False
. Note that only explicitly setting this field toFalse
keeps the current default. - To make the currently published version default, set
True
or omit this field (defaults toTrue
). If you also specify theversion
field, your request will update that version and make it default. If you omitversion
, your request will add a new app version and make that new version default.
When publishing a new app, it is safe to omit this field, and it is always assumed
True
(even if you specifyFalse
), because the initial app version is always default. thumbnail
-
The app thumbnail displayed in the app list on the main AppsHub page.
- Single field
- Optional
The
thumbnail
field content is the image file data. If you specify this field, the publish request content type must bemultipart/form-data
.If you are updating an existing app and specify this field, it will replace the app thumbnail. If you omit this field upon update, the app keeps its current thumbnail.
allow_get_as_workflow
-
Workflow copy permission, Boolean.
- Single field
- Optional
By default, the AppsHub users are allowed to copy the workflow you have published from the app to their user storage. You can restrict this for a new app, and change the setting when you update an app.
If set
True
then, in addition to running the app, the AppsHub users will be able to copy the app's workflow, edit that copy in pSeven Enterprise Studio, and distribute it.If set
False
, the AppsHub users will only be able to run the app.If omitted:
- If you are publishing a new app, defaults to
True
(allow copying). - If you are updating an existing app, keeps the current app setting.
Error handling¶
The pSeven Enterprise REST API uses standard HTTP status codes to indicate
success or failure of an API call. Successful requests return 2xx
class codes - typically 200 OK
, unless otherwise noted in examples in
this guide. Codes of the 4xx
or 5xx
class indicate an error. In this
case, the response body contains error details.
# Unauthorized request (no authorization header).
endpoint = 'workflows/'
response = requests.get(
api_url.format(endpoint),
# headers=auth_header
)
Response:
HTTP/1.1 403 Forbidden
Response data:
{"detail": "Authentication credentials were not provided."}
# Unauthorized request (no authorization header).
$ curl \
${DA__P7__REST_BASEURL}workflows/ \
-i -w "\n"
Output:
HTTP/1.1 403 Forbidden
{"detail": "Authentication credentials were not provided."}
# Attempt to get details of a workflow that does not exist.
wrong_id = '00000000000000000000000000000000'
endpoint = 'workflows/{}/'.format(wrong_id)
response = requests.get(
api_url.format(endpoint),
headers=auth_header
)
Response:
HTTP/1.1 404 Not Found
Response data:
{"detail": "Not found."}
# Attempt to get details of a workflow that does not exist.
$ curl \
-H "${DA__P7__REST_AUTHHEADER}" \
${DA__P7__REST_BASEURL}workflows/00000000000000000000000000000000/ \
-i -w "\n"
Output:
HTTP/1.1 404 Not Found
{"detail": "Not found."}