STACK
MENU
DOCS / CONCEPTS / PARAMETER CONSTRAINTS

Parameter-level constraints

Constraints narrow a Passport below a service grant. Where a grant allows Slack message posting, a constraint can limit it to channels C0123 or C0456. Constraints are evaluated on every proxy call and a failing constraint denies the request the same way an out-of-scope service does.

Constraints ride on a passport's services[].constraintsarray. A grant can carry baseline constraints that every passport inherits; passports issued from that grant can narrow further but never loosen.

Shape

A constraint is a three-tuple of path,op, and value:

json
{
  "path": "body.channel",
  "op": "in",
  "value": ["C0123", "C0456"]
}

Operators

  • eq - value at path equals the constraint value
  • not_eq - value at path does not equal
  • in - value at path is present in the constraint value (array)
  • not_in - value at path is absent from the constraint value (array)
  • matches - value at path matches the RE2 regular expression (max 256 chars)
  • starts_with - value at path starts with the constraint value (string prefix)
  • lt / lte / gt / gte - numeric comparison; numeric strings are accepted
  • exists / not_exists - require a field to be present or absent

matches uses RE2 and caps the pattern at 256 characters. RE2 rejects backtracking constructs that would enable catastrophic backtracking.

Path syntax

Paths resolve against a normalized view of the outbound request. Case-insensitive header lookup, stable URL parsing, and dotted traversal into JSON bodies are handled for you.

  • method - HTTP verb, uppercase (GET, POST, …)
  • url.pathname - path component of the URL, trailing slash stripped
  • url.host - host in lowercase
  • url.origin - scheme + host + port
  • headers.<name> - header value; name is lowercased for the lookup
  • query.<key> - query string value
  • body.<dotted.path> - JSON body traversal; supports nested fields (body.message.channel)

Evaluation semantics

  • AND - every constraint in the array must pass; one failing constraint denies
  • Max 32 constraints per ServiceScope - enforced at issue and delegate time
  • A missing path fails every value operator. Use not_exists when absence is the rule.
  • If a request field is an array, every element must satisfy the constraint.
  • String values capped at 1024 characters; array values at 256 entries of 1024 chars each

Closed operation policies

A service scope can also use a closed operation policy. Each allowed operation declares its HTTP methods, path match, permitted request fields, and optional constraints. A request is denied when it does not match a declared operation or carries an undeclared field. A scope without this policy keeps the open behavior for compatibility.

Delegation

Child constraints must be a superset of parent constraints - every parent rule rides through, and the child may add more. STACK rejects delegation with SCOPE_ESCALATION if the child drops a parent rule.

Example: lock Slack posts to two channels

bash
curl -s -X POST https://api.getstack.run/v1/services/grant \
  -H "Authorization: Bearer $STACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "agt_support_bot",
    "service_connection_id": "scon_slack_prod",
    "scopes": ["chat:write"],
    "constraints": [
      { "path": "url.pathname", "op": "eq", "value": "/api/chat.postMessage" },
      { "path": "body.channel", "op": "in", "value": ["C0123", "C0456"] }
    ]
  }'

Any passport issued against this grant can only post messages, and only to those two channels. An attempt to post to a third channel is denied at the proxy withFORBIDDEN and records acredential_outside_scope security event.

Intents vs raw constraints

Raw constraints are the primitive. Named intents are a higher-level shorthand that expand to constraints server-side - the name encodes the provider convention so you don't have to reinvent it each time. For a catalog of intents and how they expand, see/docs/concepts/intents.

stack | Docs