Skip to main content

Creating API Scenarios

An API scenario is a sequence of HTTP requests with assertions, variable extraction, and authentication. Think of it as a test case for your API.

Basic Scenario

curl -s -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
$CSS_URL/api/scenarios \
-d '{
"name": "User CRUD Lifecycle",
"projectId": 1,
"environmentId": 1,
"baseUrl": "{{baseUrl}}",
"steps": [
{
"stepOrder": 1,
"name": "Create user",
"httpMethod": "POST",
"endpointPath": "/api/users",
"requestBody": "{\"name\":\"John\",\"email\":\"john@example.com\"}",
"expectedStatusCodes": [201],
"extractions": [
{
"variableName": "userId",
"extractionType": "JSONPATH",
"extractionExpression": "$.id",
"isRequired": true
}
]
},
{
"stepOrder": 2,
"name": "Read user",
"httpMethod": "GET",
"endpointPath": "/api/users/{{userId}}",
"expectedStatusCodes": [200],
"responseAssertions": [
{
"type": "JSONPATH",
"expression": "$.name",
"operator": "EQUALS",
"expectedValue": "John"
}
]
},
{
"stepOrder": 3,
"name": "Delete user",
"httpMethod": "DELETE",
"endpointPath": "/api/users/{{userId}}",
"expectedStatusCodes": [200, 204]
}
]
}'

Key Concepts

Choosing the base URL (host)

baseUrl is optional. The recommended model: author the scenario against an application (bind a targetId, or set appTag / applicationId) and pass no host at all — the host (targetHost) resolves at run time from the environment's target for that application. Create the application and environment once, set the target's URL inside the environment, and every test authored against that app runs against whichever environment you pick, hitting that environment's URL. One scenario, every environment, zero hardcoded hosts.

Ways to supply the host, from most to least recommended:

FormResolves fromUse when
omit baseUrl + a bound target / appTagthe matching target in the run environment, at run timethe default — portable tests, host lives on the environment
"{{baseUrl}}" + an appTag (or a bound target)same as above (explicit placeholder form)equivalent to omitting; kept for existing scenarios
"{{myhost}}" (any name) + an env propertythe environment's variable baga quick test that shouldn't need a full target row
"https://api.example.com"the literal valuea fixed, single-environment host (an explicit override)

A scenario with no baseUrl and no target/appTag is refused at create time — it could never resolve a host.

The self-documenting alias "{{env.myhost}}" resolves to the same property as "{{myhost}}" — use whichever reads more clearly.

The third form (env property) is the lightest setup. Store the host once on the environment:

# set a property named `myhost` on the environment
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
$CSS_URL/api/environments/1/variables \
-d '[{"key":"myhost","value":"https://api.example.com"}]'

…then author the scenario with "baseUrl": "{{myhost}}" and no target. The host resolves from the property at run time, per environment (each env can define its own myhost). If a templated host resolves to neither a target nor a matching property, the scenario is refused at create time rather than failing on every run.

Property key rules: a key must match ^[a-zA-Z][a-zA-Z0-9_]*$ (start with a letter, alphanumeric/underscore, no dots or spaces), be ≤64 characters, and is case-insensitivemyhost and MyHost are the same key, so setting MyHost updates the existing myhost rather than creating an invisible duplicate. update_env_variable only edits an existing key (it does not create — a missing key returns 404); use set_env_variables to create.

Precedence

When a property name collides with a higher-precedence source — a resolved target's URL, or a credential field like username — the more specific source wins and the property is ignored (with a warning in the run log). Name host properties distinctly (myhost, not baseUrl) if you want them to apply unconditionally. Property-driven hosts apply to every surface — API scenarios, UI tests, performance/Artillery runs, and security scans (a scan target.value of "{{myhost}}" resolves identically).

Secrets stay in the vault

A property marked isSecret is not usable as a host or scan target — it can't be reliably masked once it lands in a URL or an Artillery config. Use secret properties only for {{var}} values inside API/UI tests, and keep real credentials in the environment credential vault (add_environment_credential), never in a property.

Variable Extraction

Extract values from responses and use them in later steps:

"extractions": [
{
"variableName": "userId",
"extractionType": "JSONPATH",
"extractionExpression": "$.id"
}
]

Reference with {{userId}} in subsequent steps — URLs, headers, bodies.

Assertions

Validate responses:

"responseAssertions": [
{"type": "JSONPATH", "expression": "$.status", "operator": "EQUALS", "expectedValue": "active"},
{"type": "JSONPATH", "expression": "$.items", "operator": "EXISTS", "expectedValue": ""},
{"type": "JSONPATH", "expression": "$.count", "operator": "GREATER_THAN", "expectedValue": "0"}
]

Operators: EQUALS, NOT_EQUALS, CONTAINS, EXISTS, NOT_EXISTS, GREATER_THAN, LESS_THAN

Authentication

Proofarc supports 5 authentication types:

TypeUse Case
Bearer (login endpoint)Most REST APIs — auto-fetches token via login
OAuth2 Client CredentialsEnterprise APIs, Auth0, Okta
Static Bearer TokenCI/CD pipelines, service accounts
Basic AuthLegacy APIs
API KeyPublic APIs with rate limiting

Example — Bearer with login endpoint:

"authConfig": {
"authType": "BEARER",
"tokenSource": "LOGIN_ENDPOINT",
"loginEndpoint": "/auth/login",
"tokenJsonPath": "$.accessToken",
"credentials": {
"username": "admin",
"password": "your-password"
}
}

YAML Format

Prefer writing scenarios in YAML? The same scenario can be written as:

name: User CRUD Lifecycle
baseUrl: "{{baseUrl}}"
steps:
- name: Create user
method: POST
path: /api/users
body:
name: John
email: john@example.com
expect: [201]
extract:
userId: $.id
- name: Read user
method: GET
path: /api/users/{{userId}}
expect: [200]
assert:
- path: $.name
equals: John
- name: Delete user
method: DELETE
path: /api/users/{{userId}}
expect: [200, 204]

Create via API: POST /api/scenarios/yaml?projectId=1&environmentId=1

See YAML Format for full reference and editor usage.

Editing Steps in the UI

Open an existing scenario and click Edit to manage its steps directly:

  • Add Step — the button at the top of the Steps section appends a new step (defaults to GET /); expand it to fill in the method, path, expected status codes, headers, body, and extractions, then click Save Step.
  • Edit — expand any step to change its fields inline and Save Step.
  • Reorder — use the up/down arrows on each step.
  • Delete — remove a step with the trash icon.

You can also import steps from a Swagger/OpenAPI spec via the Scenario Wizard.

What Happens Next

Your API scenario is a test case. From here it can:

  1. Execute → Run against any environment, get pass/fail per step
  2. Convert to Performance Test → Same endpoints, same auth, now with concurrent users
  3. Feed the Readiness Score → Pass rate contributes to your 0-100 score
  4. Map to Compliance → Test execution evidence auto-maps to SOC 2, PCI-DSS controls