Skip to content

Actors

An actor is an identity your API recognises — an admin, a vendor, a customer, a worker service. You describe how to become that identity once, and every operation that runs as it gets credentials automatically.

That’s the difference between Reqloom and an HTTP client. In Postman you paste a token into a variable and refresh it by hand when it expires. Here, the login is part of the schema and the engine runs it when it’s needed.

actors/vendor.yaml
name: vendor
description: Marketplace vendor with email/password auth
auth: # how to become this identity
strategy: simple
method: POST
path: /api/v1/auth/vendor/login
body:
email: "{{env.vendor_email}}"
password: "{{env.vendor_password}}"
expect_status: 200
extract:
token: $.data.accessToken
refresh_token: $.data.refreshToken
vendor_id: $.data.user.id
session: # how long credentials stay valid
ttl: 15m
inject: # what to attach to every request
headers:
Authorization: "Bearer {{vendor.token}}"

Three parts, and each answers a separate question:

  • auth — what request(s) produce a session, and what to pull out of the response
  • session — how long to reuse it before authenticating again
  • inject — what every operation running as this actor should carry

Name it on the operation:

resources/products.yaml
name: product
operations:
create:
method: POST
path: /api/v1/vendors/{{vendor.vendor_id}}/products
actor: vendor
expect_status: 201
extract:
product_id: $.data.id

Two things happened there. actor: vendor means the Authorization header from inject is attached. And {{vendor.vendor_id}} uses a value the login response produced — extractions aren’t limited to tokens. Anything the auth response returns is available as {{<actor>.<name>}} anywhere in the schema.

Omit actor: for a public endpoint. No auth is attached and no login runs:

list:
method: GET
path: /api/v1/products

Because a real workflow crosses identities. Approving a refund in the sample project touches three:

Terminal window
Running: refund.approve (chain of 7 steps, env=local)
[1] Running: product.create (vendor)
[2] Running: product.publish (vendor)
[3] Running: cart.add_item (customer)
[4] Running: order.create (customer)
[5] Running: order.pay (customer)
[6] Running: refund.request (customer)
[7] Running: refund.approve (admin)

Three logins happen as part of that run, and none of them appear as chain steps — a login is part of the step that needs it. You never asked for them, and you never copied a token.

The simple strategy above is one login request. Real APIs vary, so there are eleven, set with auth.strategy:

StrategyUse whenNetwork call
simpleOne login request returns a tokenyes
chainLogin takes several steps — OTP, MFA, tenant selectyes
basicHTTP Basicno
api_keyA static key in a header, query param, or cookieno
bearerYou already hold the tokenno
oauth2_client_credentialsMachine-to-machine OAuth 2yes
oauth2_passwordOAuth 2 resource-owner password grantyes
oauth1OAuth 1.0a, HMAC-SHA1 signed per requestno
aws_sigv4AWS Signature v4no
jwtYou sign your own JWTno
mtlsClient-certificate TLSno

strategy defaults to simple.

Several need no request at all, because the credential already exists:

actors/service.yaml — pre-issued token, no login
name: service
description: CI service account using a long-lived token
auth:
strategy: bearer
token: "{{secret.SERVICE_TOKEN}}"
session:
ttl: 24h

Multi-step logins use chain, where each step can use what the previous extracted:

actors/customer.yaml — OTP flow
name: customer
auth:
strategy: chain
steps:
- id: request_otp
method: POST
path: /api/v1/auth/otp/request
body:
phone: "{{env.test_phone}}"
expect_status: 200
- id: verify_otp
method: POST
path: /api/v1/auth/otp/verify
body:
phone: "{{env.test_phone}}"
code: "{{env.test_otp}}"
expect_status: 200
extract:
token: $.data.accessToken
customer_id: $.data.user.id
inject:
headers:
Authorization: "Bearer {{customer.token}}"

See auth strategies for the exact keys each one reads.

One actor per identity, not per credential

Section titled “One actor per identity, not per credential”

The useful boundary is who the API thinks you are, not which token you hold.

Same credential, different acting identity — like Stripe’s Stripe-Account header — is still two actors, because the API treats them differently:

actors/platform.yaml
name: platform
auth:
strategy: bearer
token: "{{secret.STRIPE_KEY}}"
inject:
headers:
Authorization: "Bearer {{platform.token}}"
actors/connected.yaml
name: connected
auth:
strategy: bearer
token: "{{secret.STRIPE_KEY}}"
inject:
headers:
Authorization: "Bearer {{connected.token}}"
Stripe-Account: "{{env.connected_account_id}}"

Conversely, don’t create admin_readonly and admin_write if the API issues them the same identity — that’s one actor, and the difference belongs in the request.

An actor authenticates once per session lifetime, not once per operation. Seven steps as one customer means one login. See sessions & caching for TTL, refresh, and expiry.

Never in the schema. Environment variables hold the non-sensitive half and !secret binds the rest to your OS keychain:

environments/local.yaml
name: local
variables:
vendor_email: vendor@marketplace.test
vendor_password: !secret VENDOR_PASSWORD

The schema stays committable. See secrets.