From c1272b3701198c87010855a657914dd7682a360f Mon Sep 17 00:00:00 2001 From: Suchintan Date: Fri, 13 Feb 2026 00:30:46 -0500 Subject: [PATCH] Improve search_key/error_code API docs and reorganize Fern sections (#SKY-7884) (#4740) Co-authored-by: Suchintan Singh --- fern/docs.yml | 14 ++- skyvern/forge/sdk/routes/agent_protocol.py | 140 +++++++++++++++++---- 2 files changed, 123 insertions(+), 31 deletions(-) diff --git a/fern/docs.yml b/fern/docs.yml index befe138e..b996974e 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -176,15 +176,20 @@ navigation: hidden: true - GET /v1/runs/{run_id}/timeline - section: Workflows + contents: + - GET /v1/workflows + - POST /v1/workflows + - GET /v1/workflows/{workflow_permanent_id} + - GET /v1/workflows/{workflow_permanent_id}/versions + - POST /v1/workflows/{workflow_id} + - POST /v1/workflows/{workflow_id}/delete + - section: Workflow Runs contents: - POST /v1/run/workflows + - GET /v1/workflows/runs - GET /v1/runs/{run_id} - POST /v1/runs/{run_id}/cancel - POST /v1/runs/{run_id}/retry_webhook - - GET /v1/workflows - - POST /v1/workflows - - POST /v1/workflows/{workflow_id} - - POST /v1/workflows/{workflow_id}/delete - GET /v1/runs/{run_id}/timeline - section: Browser Sessions contents: @@ -202,6 +207,7 @@ navigation: contents: - POST /v1/credentials - GET /v1/credentials/{credential_id} + - POST /v1/credentials/{credential_id}/update - POST /v1/credentials/{credential_id}/delete - GET /v1/credentials - POST /v1/credentials/totp diff --git a/skyvern/forge/sdk/routes/agent_protocol.py b/skyvern/forge/sdk/routes/agent_protocol.py index daf7358d..9840cec8 100644 --- a/skyvern/forge/sdk/routes/agent_protocol.py +++ b/skyvern/forge/sdk/routes/agent_protocol.py @@ -333,7 +333,7 @@ async def run_task( @base_router.post( "/run/workflows", - tags=["Workflows"], + tags=["Workflow Runs"], openapi_extra={ "x-fern-sdk-method-name": "run_workflow", "x-fern-examples": [ @@ -438,7 +438,7 @@ async def run_workflow( @base_router.get( "/runs/{run_id}", - tags=["Agent", "Workflows"], + tags=["Agent", "Workflow Runs"], response_model=RunResponse, description="Get run information (task run, workflow run)", summary="Get a run by id", @@ -480,7 +480,7 @@ async def get_run( @base_router.post( "/runs/{run_id}/cancel", - tags=["Agent", "Workflows"], + tags=["Agent", "Workflow Runs"], openapi_extra={ "x-fern-sdk-method-name": "cancel_run", "x-fern-examples": [ @@ -1444,7 +1444,7 @@ async def get_run_artifacts( @base_router.post( "/runs/{run_id}/retry_webhook", - tags=["Agent"], + tags=["Agent", "Workflow Runs"], openapi_extra={ "x-fern-sdk-method-name": "retry_run_webhook", "x-fern-examples": [ @@ -1477,7 +1477,7 @@ async def retry_run_webhook( @base_router.get( "/runs/{run_id}/timeline", - tags=["Agent", "Workflows"], + tags=["Agent", "Workflow Runs"], response_model=list[WorkflowRunTimeline], openapi_extra={ "x-fern-sdk-method-name": "get_run_timeline", @@ -1992,7 +1992,13 @@ async def get_runs( status: Annotated[list[WorkflowRunStatus] | None, Query()] = None, search_key: str | None = Query( None, - description="Search runs by run ID, parameter key, parameter description, run parameter value, or extra HTTP headers.", + description=( + "Case-insensitive substring search across: workflow run ID, " + "parameter key, parameter description, run parameter value, " + "and extra HTTP headers. A run is returned if any of these fields match. " + "Soft-deleted parameter definitions are excluded from key/description matching." + ), + examples=["login_url", "credential_value", "wr_abc123"], ), ) -> Response: analytics.capture("skyvern-oss-agent-runs-get") @@ -2205,11 +2211,32 @@ async def run_workflow_legacy( @base_router.get( "/workflows/runs", response_model=list[WorkflowRun], - tags=["Workflows"], + tags=["Workflow Runs"], description=( - "List workflow runs across all workflows for the current organization. " - "Results are paginated and can be filtered by status, search key, and error code. " - "All filters are combined with AND logic." + "List workflow runs across all workflows for the current organization.\n\n" + "Results are paginated and can be filtered by **status**, **search_key**, and **error_code**. " + "All filters are combined with **AND** logic — a run must match every supplied filter to be returned.\n\n" + "### search_key\n\n" + "A case-insensitive substring search that matches against **any** of the following fields:\n\n" + "| Searched field | Description |\n" + "|---|---|\n" + "| `workflow_run_id` | The unique run identifier (e.g. `wr_123…`) |\n" + "| Parameter **key** | The `key` of any workflow parameter definition associated with the run |\n" + "| Parameter **description** | The `description` of any workflow parameter definition |\n" + "| Run parameter **value** | The actual value supplied for any parameter when the run was created |\n" + "| `extra_http_headers` | Extra HTTP headers attached to the run (searched as raw JSON text) |\n\n" + "Soft-deleted parameter definitions are excluded from key/description matching. " + "A run is returned if **any** of the fields above contain the search term.\n\n" + "### error_code\n\n" + "An **exact-match** filter against the `error_code` field inside each task's `errors` JSON array. " + "A run matches if **any** of its tasks contains an error object with a matching `error_code` value. " + "Error codes are user-defined strings set during workflow execution " + "(e.g. `INVALID_CREDENTIALS`, `LOGIN_FAILED`, `CAPTCHA_DETECTED`).\n\n" + "### Combining filters\n\n" + "All query parameters use AND logic:\n" + "- `?status=failed` — only failed runs\n" + "- `?status=failed&error_code=LOGIN_FAILED` — failed runs **and** have a LOGIN_FAILED error\n" + "- `?status=failed&error_code=LOGIN_FAILED&search_key=prod_credential` — all three conditions must match" ), summary="List workflow runs", openapi_extra={ @@ -2241,14 +2268,22 @@ async def get_workflow_runs( search_key: str | None = Query( None, max_length=500, - description="Search runs by run ID, parameter key, parameter description, run parameter value, or extra HTTP headers.", - examples=["login_url", "credential_value"], + description=( + "Case-insensitive substring search across: workflow run ID, " + "parameter key, parameter description, run parameter value, " + "and extra HTTP headers. A run is returned if any of these fields match. " + "Soft-deleted parameter definitions are excluded from key/description matching." + ), + examples=["login_url", "credential_value", "wr_abc123"], ), error_code: str | None = Query( None, max_length=500, - description="Filter runs by user-defined error code stored in task errors. " - "Matches against the error_code field in the task errors JSON array.", + description=( + "Exact-match filter on the error_code field inside each task's errors JSON array. " + "A run matches if any of its tasks contains an error with a matching error_code. " + "Error codes are user-defined strings set during workflow execution." + ), examples=["INVALID_CREDENTIALS", "LOGIN_FAILED", "CAPTCHA_DETECTED"], ), current_org: Organization = Depends(org_auth_service.get_current_org), @@ -2256,13 +2291,22 @@ async def get_workflow_runs( """ List workflow runs across all workflows for the current organization. - Results are paginated and can be filtered by status, search key, and error code. - All filters are combined with AND logic. + All filters (**status**, **search_key**, **error_code**) are combined with AND logic. + + **search_key** performs a case-insensitive substring match across: + - `workflow_run_id` — the unique run identifier + - Parameter **key** and **description** from workflow parameter definitions (soft-deleted parameters excluded) + - Run parameter **value** — the actual value supplied when the run was created + - `extra_http_headers` — searched as raw JSON text + + **error_code** performs an exact match against the `error_code` field in the `errors` + JSON array on each task. A run matches if *any* of its tasks has a matching error code. **Examples:** - All failed runs: `?status=failed` - Failed runs with a specific error: `?status=failed&error_code=INVALID_CREDENTIALS` - Runs matching a parameter value: `?search_key=https://example.com` + - Runs matching a run ID: `?search_key=wr_abc123` - Combined: `?status=failed&error_code=LOGIN_FAILED&search_key=my_credential` """ analytics.capture("skyvern-oss-agent-workflow-runs-get") @@ -2280,6 +2324,19 @@ async def get_workflow_runs( "/workflows/{workflow_id}/runs", response_model=list[WorkflowRun], tags=["agent"], + description=( + "List runs for a specific workflow.\n\n" + "Supports filtering by **status**, **search_key**, and **error_code**. " + "All filters are combined with **AND** logic.\n\n" + "### search_key\n\n" + "Case-insensitive substring search across: workflow run ID, " + "parameter key, parameter description, run parameter value, " + "and extra HTTP headers. Soft-deleted parameter definitions are excluded.\n\n" + "### error_code\n\n" + "Exact-match filter on the `error_code` field inside each task's `errors` JSON array. " + "A run matches if any of its tasks contains an error with a matching `error_code`." + ), + summary="List runs for a workflow", openapi_extra={ "x-fern-sdk-method-name": "get_workflow_runs_by_id", }, @@ -2291,33 +2348,51 @@ async def get_workflow_runs( ) async def get_workflow_runs_by_id( workflow_id: str, - page: int = Query(1, ge=1), - page_size: int = Query(10, ge=1), - status: Annotated[list[WorkflowRunStatus] | None, Query()] = None, + page: int = Query(1, ge=1, description="Page number for pagination."), + page_size: int = Query(10, ge=1, description="Number of runs to return per page."), + status: Annotated[list[WorkflowRunStatus] | None, Query(description="Filter by one or more run statuses.")] = None, search_key: str | None = Query( None, max_length=500, - description="Search runs by run ID, parameter key, parameter description, run parameter value, or extra HTTP headers.", + description=( + "Case-insensitive substring search across: workflow run ID, " + "parameter key, parameter description, run parameter value, " + "and extra HTTP headers. A run is returned if any of these fields match. " + "Soft-deleted parameter definitions are excluded from key/description matching." + ), + examples=["login_url", "credential_value", "wr_abc123"], ), error_code: str | None = Query( None, max_length=500, - description="Filter runs by user-defined error code stored in task errors. " - "Matches against the error_code field in the task errors JSON array.", + description=( + "Exact-match filter on the error_code field inside each task's errors JSON array. " + "A run matches if any of its tasks contains an error with a matching error_code. " + "Error codes are user-defined strings set during workflow execution." + ), examples=["INVALID_CREDENTIALS", "LOGIN_FAILED", "CAPTCHA_DETECTED"], ), current_org: Organization = Depends(org_auth_service.get_current_org), ) -> list[WorkflowRun]: """ - Get workflow runs for a specific workflow permanent id. + List runs for a specific workflow permanent id. - Supports filtering by status, parameter search, and error code. All filters - are combined with AND logic. + All filters (**status**, **search_key**, **error_code**) are combined with AND logic. + + **search_key** performs a case-insensitive substring match across: + - `workflow_run_id` — the unique run identifier + - Parameter **key** and **description** from workflow parameter definitions (soft-deleted parameters excluded) + - Run parameter **value** — the actual value supplied when the run was created + - `extra_http_headers` — searched as raw JSON text + + **error_code** performs an exact match against the `error_code` field in the `errors` + JSON array on each task. A run matches if *any* of its tasks has a matching error code. **Examples:** - All failed runs: `?status=failed` - Failed runs with a specific error: `?status=failed&error_code=INVALID_CREDENTIALS` - Runs matching a parameter value: `?search_key=https://example.com` + - Runs matching a run ID: `?search_key=wr_abc123` - Combined: `?status=failed&error_code=LOGIN_FAILED&search_key=my_credential` """ analytics.capture("skyvern-oss-agent-workflow-runs-get") @@ -2487,9 +2562,20 @@ async def get_workflows( only_templates: bool = Query(False), search_key: str | None = Query( None, - description="Unified search across workflow title, folder name, and parameter metadata (key, description, default_value).", + description=( + "Case-insensitive substring search across: workflow title, folder name, " + "and parameter metadata (key, description, default_value). " + "A workflow is returned if any of these fields match. " + "Soft-deleted parameter definitions are excluded. " + "Takes precedence over the deprecated `title` parameter." + ), + examples=["my_workflow", "login_url", "production"], + ), + title: str = Query( + "", + deprecated=True, + description="Deprecated: use search_key instead. Falls back to title-only search if search_key is not provided.", ), - title: str = Query("", deprecated=True, description="Deprecated: use search_key instead."), folder_id: str | None = Query(None, description="Filter workflows by folder ID"), status: Annotated[list[WorkflowStatus] | None, Query()] = None, current_org: Organization = Depends(org_auth_service.get_current_org),