---
title: "Record something the user did, for the agent running this session"
method: POST
path: "/agentSession/{id}/context/actions"
tags: ["AgentSession"]
---

# Record something the user did, for the agent running this session

`POST /agentSession/{id}/context/actions`

## Request

- Base URL: `https://api.mabl.com`
- URL: `https://api.mabl.com/agentSession/{id}/context/actions`
- Auth: one of:
  - HTTP basic
  - API key in header `Authorization`

Tells the agent running this session that the user did something in a client — edited the test configuration it saved, pointed the session at a test, a test case or a tracker issue, or attached a file — and does the server-side work that makes it true.
Separate from the inbox because the inbox is a caller speaking. The sender recorded there is derived from the authenticated caller precisely so a caller cannot claim a person typed something, which leaves a client with no way to report a user action except to write prose and send it as the user's own words. These actions are mabl speaking: the caller names an action and its metadata, the server composes the text and queues it as `system`, so what the model reads is versioned with the API rather than assembled in each client.
The server also does the work the action implies, and records none of it on the session. An agent session's artifacts belong to the turn that is running — a turn writes its whole merged set, built from what it loaded, and artifacts merge by name — so anything written into them from outside is replaced by that turn's stale copy. Nothing here writes one.
An attachment's bytes go to the workspace's own storage, under a path derived from the session and the id and recorded nowhere. The agent finds the file by the id this message gives it, so a caller never supplies and never learns a location: one it supplied would be forged by definition, and there is no string that would resolve anywhere else.
A reference is announced and validated, not stored. Reading one back still takes the reference list a planning client maintains; an agent keeping its own note of what it was told is a separate change.

## Path parameters

- `id` string, required

## Request body

- RecordAgentSessionContextActionRequest: One thing the user did. Deliberately carries no message text and no storage location: the text is composed by the server from the action and its metadata, which is what keeps model-facing wording versioned with the API rather than written in each client, and the location is minted by the server.
  - `action` 'test_configuration_edited' | 'test_referenced' | 'test_case_referenced' | 'xray_test_referenced' | 'tracker_issue_referenced' | 'file_attached' | 'page_viewed', required: What the user did. A closed set, because each member decides what the server does and what it says to the agent — there is no member meaning "something else, here is the text", which is the thing this endpoint exists instead of. `test_configuration_edited` is the user changing the configuration the agent saved, and is the one action that asks the agent to do something rather than merely telling it something. The four reference actions each name one thing the user pointed the session at. `file_attached` stores the file where the agent can read it by id. `page_viewed` says which page the user is on, which is the one action describing a state rather than an event: it is re-sent when they navigate, and each one replaces the last.
  - `test_configuration` TestCreationPlanningTestInformation: Test configuration and metadata selected during planning
    - `test_id` string: The ID of an existing test to edit instead of creating a new one
    - `test_variant_id` string: The variant (version) ID of the test being edited. Distinct from `test_id`, which is the invariant test identity — `test_variant_id` pins a specific snapshot. Set when the caller wants the edit applied against a known version (for example, to dedup auto-triggered edits for the same version).
    - `source_test_id` string: The invariant ID of an existing test to COPY as the starting point for a brand new test. The source test is only read — it is never modified — and the test that gets created is a new, independent test. Mutually exclusive with `test_id`, which edits an existing test in place rather than copying it. Set this when the user wants to start from an existing test ("make a copy of X and then ...") and leave `test_id` unset.
    - `name` string: The name of the test
    - `description` string: The description of the test
    - `application_id` string: The ID of the application for the test
    - `environment_id` string: The ID of the environment for the test
    - `deployment_id` string: The ID of the deployment for the test
    - `test_case` string: The test case for the test
    - `url_override` string: The base URL for the test, if there is no application or if the application's base URL should be overridden for this test
    - `credentials_id` string: The ID of the credentials for the test
    - `http_auth_credentials_id` string: The ID of the HTTP Basic Auth credentials for the test - passed in the HTTP request, as opposed to the interactive login credentials in credentials_id
    - `test_type` 'api' | 'browser' | 'performance' | 'mobile' | 'desktop': Type of test
    - `viewport_width` integer: Browser viewport width in pixels
    - `viewport_height` integer: Browser viewport height in pixels
    - `datatable_id` string: The ID of the data table for data-driven tests
    - `scenario_id` string: The ID of a specific scenario within the data table
    - `labels` string[]: Labels to apply to the test
    - `branch_id` string: The ID of the branch to save the test to (the save-to / destination branch)
    - `source_branch_id` string: The ID of the branch to load the test, or the flow for a flow authoring edit, from as the starting point for edits. Only meaningful when test_id or flow_id is set (editing an existing test or flow). When unset, the test is loaded from master (it does NOT fall back to branch_id). This is the load-from branch; branch_id remains the save-to (destination) branch.
    - `plan_id` string: The ID of the plan to add the test to after creation
    - `device_preset` 'blackberry_playbook' | 'blackberry_z30' | 'galaxy_fold' | 'galaxy_note_3' | 'galaxy_note_ii' | 'galaxy_s5' | 'galaxy_s6' | 'galaxy_s8' | 'galaxy_s21' | 'galaxy_s22' | 'galaxy_s23' | 'galaxy_s_iii' | 'galaxy_tab_s7' | 'ipad' | 'ipad_air_2020' | 'ipad_mini' | 'ipad_mini_4' | 'ipad_pro' | 'ipad_ten_point_two' | 'iphone_11_pro_max' | 'iphone_11' | 'iphone_12' | 'iphone_12_pro_max' | 'iphone_13_mini' | 'iphone_13' | 'iphone_13_pro' | 'iphone_13_pro_max' | 'iphone_14' | 'iphone_14_pro' | 'iphone_14_plus' | 'iphone_14_pro_max' | 'iphone_15' | 'iphone_15_plus' | 'iphone_15_pro' | 'iphone_15_pro_max' | 'iphone_4' | 'iphone_5_se' | 'iphone_6_7_8' | 'iphone_6_7_8_plus' | 'iphone_x' | 'jiophone_2' | 'kindle_fire_hdx' | 'laptop_with_hidpi_screen' | 'laptop_with_mdpi_screen' | 'laptop_with_touch' | 'lg_optimus_l70' | 'microsoft_lumia_550' | 'microsoft_lumia_950' | 'moto_g4' | 'nexus_4' | 'nexus_5' | 'nexus_5x' | 'nexus_6' | 'nexus_6p' | 'nexus_7' | 'nexus_10' | 'nokia_lumia_520' | 'nokia_n9' | 'pixel_2' | 'pixel_2_xl' | 'pixel_6' | 'surface_duo': Preset device configuration
    - `device_orientation` 'portrait_primary' | 'portrait_secondary' | 'landscape_primary' | 'landscape_secondary': Device orientation
    - `flow_id` string: Flow authoring only. The invariant ID of an existing reusable flow to edit instead of creating a new one. A flow authoring session rejects the test-only fields, such as test_id and source_test_id.
    - `setup_test_id` string: Flow authoring only. The invariant ID of a test that reaches the flow's start state. On an edit, its steps before its first call to the flow seed the session as setup; on a create, the agent imports its steps up to the start state. Setup steps are never saved. When unset on a flow edit, the server picks a test that uses the flow.
    - `test_objective` TestObjective: What this test version is trying to accomplish, in natural language. Version-scoped, so each version records the objective it was authored against rather than sharing one across the whole test's history. Saved through test_objective: a full save may set it alongside steps as one version, and omitting it keeps the stored one. A field_mask=test_objective write may only record state and gap; changing summary or source that way is refused, so use a full save.
      - `summary` string: A natural language summary of what this test version does. Omitting it on a save keeps the stored one; sending it empty removes the objective outright, since source, state, and gap all describe a summary.
      - `source` 'user' | 'agent': Who authored the objective summary.
      - `state` 'unverified' | 'aligned' | 'drifted' | 'accepted_gap': Whether the steps of this test version have been checked against the objective summary, and what the check found. unverified means the summary has not been checked against these steps; aligned means it was checked and the steps satisfy it; drifted means it was checked and the steps fall short, with the difference in gap; accepted_gap means the user saw that gap and chose to keep it. Scoped to the step list stored on this version. Steps reached through a referenced flow are not covered: a flow is held by ID and resolved to a version at run time, and that resolution depends on the branch, so editing a referenced flow, or reading this version from a branch that resolves it differently, can leave the state describing steps the check never saw.
      - `gap` string: When state is drifted or accepted_gap, how the steps of this version fall short of the summary. Retained after the user accepts the gap so the accepted difference stays readable. Unindexed: free prose nothing queries, and long enough to exceed Datastore's 1500-byte ceiling on an indexed string.
  - `configuration_origin` 'live' | 'saved': Which configuration the user edited, which is the only thing that changes how the edit is put to the agent. `live` is the configuration the conversation is working on. `saved` is a historical snapshot of a configuration the agent already saved, where editing it cannot rewrite anything, so the agent is told to adopt the values rather than that its own working copy moved.
  - `reference` AgentSessionContextReference: The thing the user pointed the session at. One shape for every kind rather than a variant per action, mirroring the one the clients already use: which fields are read is decided by the action, and a field belonging to another action is ignored rather than refused.
    - `test_id` string: `test_referenced`: the invariant id of the mabl test.
    - `case_identifier` string: `test_case_referenced`: the case as its provider names it — a TestRail case id such as `C1234`, or a Zephyr case key such as `PROJ-T12`. This is what the reading tool takes.
    - `case_provider` 'testrail' | 'zephyr': Which test case management system `case_identifier` belongs to. Both are read by the same tool, but they are named separately because their identifiers are not interchangeable and the integration behind each resolves differently.
    - `issue_id` string: `xray_test_referenced`: the numeric Jira issue id, which is what Xray's API resolves. Not the key, which nobody but a person uses.
    - `issue_key` string: `xray_test_referenced` and `tracker_issue_referenced`: the key the user picked it by, such as `PROJ-123` or `4815`. For a tracker issue this is what the API reads; for an Xray test it is carried only so the agent can name the test the way the user does.
    - `platform` 'azure_boards' | 'jira': Which tracker a `tracker_issue_referenced` issue lives in.
    - `workspace_integration_id` string: Which integration holds it. Required for every kind but a mabl test: a case id is unique only within one instance and an issue id only within one site, so a reference without this cannot be read — and must never be resolved by trying each integration in turn, which reads whichever one happens to hold that id.
    - `label` string: What the user sees it called. Display only — the agent is told it so it can talk about the thing the way the user does, never so it can look it up.
    - `url` string: Where it lives, when the client knows. Recorded on the reference; not fetched.
  - `attachment` AgentSessionContextAttachment: A file the user attached. The bytes ride inline on this request and are written to the workspace's storage before anything is persisted on the session, so raw content never reaches the session row — which is rewritten on every turn, and would therefore be paid for on every turn. There is deliberately no field for where it is stored. The server mints the location from the session and the id, and the response does not report it: a caller has no way to legitimately know a storage location, so one supplied by a caller would be forged by definition.
    - `id` string, required: How the agent will name this attachment. Chosen by the caller so that a retry resolves to the file already stored rather than storing a second copy; re-sending an id that this session already holds returns the stored file and writes nothing.
    - `filename` string, required: What the user calls the file. Shown to the agent and stored as the object's own metadata; nothing is derived from it, and it is not part of where the file lands.
    - `mime_type` string, required: The file's type, which decides both whether it can be attached at all and how large it may be — a text file is read into the conversation whole, while a PDF reaches the model as media at a fixed cost per page.
    - `content` string, required: The file itself, base64-encoded. The outer bound here is only what a request body may carry; the ceiling that applies is the one for this file's type, and both it and the per-session totals are enforced on the way in.
  - `page` AgentSessionContextPage: Where the user is while they talk to the agent, so a workspace-scoped assistant knows which run or deployment "this one" means. State rather than an event, and the only action here that is: it is re-sent when the user navigates, and each one supersedes the last. That is why it carries the page rather than a change to it, and why the composed message says plainly that it replaces any earlier one. An entity the agent can already read by id. Nothing is fetched here and nothing is stored on the session: this says what the user is looking at, and the agent reads it with the tool for that kind if it needs to.
    - `entity_type` 'test_run' | 'plan_run' | 'deployment' | 'pull_request', required: What the page is about. A closed set for the same reason the actions are: each member decides what the server says to the agent, and a member meaning "something else" would put the wording back in the client.
    - `entity_id` string, required: The id of the thing the page is about, in the form its own tool takes.
    - `label` string: What the page calls it. Display only, so the agent can refer to it the way the user does -- never so it can look it up.
    - `url` string: The page the user is on, when the client knows it. Recorded, never fetched.
  - `idempotency_key` string: Makes a retry safe, on the same terms as the inbox: the same key to the same session twice returns the message stored the first time and queues nothing new. The work behind the action is idempotent on its own — a re-sent attachment id resolves to the file already stored under it, and a reference action stores nothing to repeat.

## Response `200`

The queued message, plus the identifier the agent will know the referenced or attached thing by. On a repeat of an idempotency key this is the message stored the first time, and nothing new was queued.

- AgentSessionContextActionResult: What was queued, and what the agent will know the named thing by.
  - `message` AgentInboxMessage, required: A message queued for an agent. Written by whoever may send to the session, read later by the agent itself. Immutable once stored: the only way to change what an agent was told is to send it something else.
    - `message_index` integer, required: Position in this session's inbox, assigned by the server and dense from zero. Ordering is the point: an agent reads what it was told in the order it was told, and the index is what a cursor moves through.
    - `message_id` string, required: Server-assigned id for this message.
    - `sender_id` string, required: The authenticated caller who sent it, never anything the request body said. Recorded so a message can be attributed after the fact.
    - `sender_type` 'user' | 'system' | 'agent', required: Who the message is from, which decides how it reaches the model. `user` is a person, and their text is marked as human authorship. `system` is mabl speaking — a notification, a trigger — and is deliberately NOT marked that way, so an agent is never fed its own machinery back as "what the user asked for". `agent` is another agent session speaking, which names itself in `sender_agent_session_id`. Derived from the authenticated caller, never sent: a caller with nobody behind it cannot claim a person typed something. An agent's credential names its session, and that is the more specific fact about a caller who is also acting for a person — so a credential naming a session is read as `agent` rather than as the person it acts on behalf of.
    - `sender_agent_session_id` string: The agent session this came from, when an agent is speaking. Read off the caller's verified credential rather than the request body, so it attests the topology rather than asserting it. Absent for any other sender.
    - `message` string, required: What the agent is being told. Bounded because it becomes a permanent turn in a conversation that is re-sent to the model on every turn after it, not merely a request body.
    - `idempotency_key` string: The key this message was stored under, when one was supplied.
    - `created_time` integer, required: Commit time, assigned by the database rather than the caller.
  - `identifier` string: What the agent will call the thing this action named — a test id, a case identifier, the session's own identifier for a tracker issue, or an attachment id. Assigned by the server for a tracker issue, because two issues from different trackers can present the same key and an identifier the agent has already read must never come to mean a different item. Absent for `test_configuration_edited`, which names nothing.

## Other responses

- `401`: User not authenticated
- `403`: User not authorized
- `404`: Entity not found
- `default`: Unknown error

## Changes

- **2026-10-19** `b602223aa51f`: 5 info
  - added the new optional request property `test_configuration/allOf[#/components/schemas/TestCreationPlanningTestInformation]/test_objective`
  - added the optional property `guardrail/allOf[#/components/schemas/GuardrailRefusal]/approval` to the response with the `401` status
  - added the optional property `guardrail/allOf[#/components/schemas/GuardrailRefusal]/approval` to the response with the `403` status
  - added the optional property `guardrail/allOf[#/components/schemas/GuardrailRefusal]/approval` to the response with the `404` status
  - …1 more
- **2026-10-17** `91ff383fb7f9`: 1 info
  - added the new `jira` enum value to the request property `reference/platform`
- **2026-10-15** `4e32d22292af`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/mabl/apis/mabl-api/changes/agentSession/:id/context/actions/post.md)

---

[API](https://skmtc.dev/mabl/apis/mabl-api.md) · [All operations](https://skmtc.dev/mabl/apis/mabl-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mabl/apis/mabl-api/revisions/b602223aa51f?raw)
