> ## Documentation Index
> Fetch the complete documentation index at: https://docs.noetive.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Ask whether this agent may take an action

> An [OpenID AuthZEN 1.0](https://openid.net/specs/authorization-api-1_0.html)
Access Evaluation request, answered by the policy that governs the
calling agent: the organization's rules, the tenant's, and the agent's
own, under the roles the agent holds.

Any agent credential may call it (a key, or an access token for the
audience `https://access.noetive.io`). It never changes anything: a
decision is not an approval, and a service that enforces policy itself
(such as mail) makes its own decision.

**What you send, and what you cannot.** `subject.id` must be the agent
the credential belongs to, otherwise the request is `403 forbidden`.
The subject carries nothing else: who the agent is, its tenant, roles
and organization come from its credential and are looked up by the
server. The resource `type` is fixed by the action (see the table
below) and `id` names it; the server finds its owner and the rest of
what it needs. Only a `Commitment` accepts `properties`, its `amount`.
The only `context` you may send is `declared`. A request that supplies
anything else, such as a role, a recipient flag or an authority mode,
is `400 invalid_request`: it is refused, never ignored.

**What the answer means.** `decision` is `true` only when `tier` is
`allowed`. `tier` is `requires_approval` when a person must approve the
action first (file it with `approvals/v1`), and `denied` when it can
never be done as asked. `rule_ids` are the ids of the rules that
decided and `messages` their explanations, both empty when nothing
matched (the default is denied). Policy source is never returned.

What you declare can only describe the request, never relax it: a
declared classification can raise the sensitivity of a message and not
lower it, and a rule that forbids the action applies whatever is
declared. An action decided per recipient (`mail.send`, `mail.send_as`,
`calendar.attendee.add`) cannot be evaluated here, because recipients
are known only to the service that sends: it answers `503
policy_unavailable`.

| Action | `resource.type` |
| --- | --- |
| `mail.read`, `mail.modify`, `mail.delete`, `mailbox.manage`, `mail.send`, `mail.send_as` | `Mailbox` (an agent mailbox id) |
| `attachment.read` | `Attachment` |
| `calendar.read`, `calendar.freebusy.read`, `calendar.event.create` | `Calendar` |
| `calendar.event.update`, `calendar.event.cancel`, `calendar.event.respond`, `calendar.attendee.add` | `CalendarEvent` |
| `contact.read`, `contact.create` | `AddressBook` |
| `contact.update`, `contact.delete` | `Contact` |
| `directory.read`, `directory.export`, `agent.provision` | `Tenant` (your own tenant id) |
| `authority.read`, `authority.modify` | `Authority` |
| `commitment.create` | `Commitment` |
| `semantik.publish`, `semantik.search`, `semantik.subscribe` | `Namespace` (a namespace id of your tenant) |

A resource the tenant does not own, or one the server cannot load,
is answered `denied`.




## OpenAPI

````yaml /api/access-api.yaml post /access/v1/evaluation
openapi: 3.0.3
info:
  title: Noetive Access API (agent-facing)
  version: 0.1.0
  description: >
    The agent-facing surface of `access.noetive.io`: what a program holding an

    agent credential calls.


    This revision documents **`agents/v1`**: create agents, their keys and

    their mailboxes from your own service, without the dashboard; and

    **`access/v1`**: ask whether an agent may take an action, as an OpenID

    AuthZEN 1.0 policy decision point. `approvals/v1` is served from the same

    host and is documented in a later revision of this document.


    Every operation is a JSON `POST`, including the ones that only read, except

    the public discovery document, which is a `GET`. Any

    other method on a known operation is answered `405`, and an operation that

    does not exist is answered `404` `not_found`.


    ## Who may call it


    The caller is an agent with **May create agents and keys** turned on by a

    person in the dashboard. That agent is the *provisioner*. It presents one

    of:


    - a static key (`keya_…`) as `Authorization: Bearer <key>`;

    - an access token (a JWT) for the audience `https://access.noetive.io`
      carrying the scope `agent:provision`. The token endpoint grants that
      scope only to an agent that may create agents; a token without it is
      answered `403` `insufficient_scope`.

    Everything a provisioner creates belongs to the provisioner's own tenant.

    No request field names a tenant, so a credential can never create anything

    in another tenant.


    ## What a provisioner may do


    - **It never grants more than it holds.** An agent it creates reaches at
      most the Bud scopes, namespaces, global namespaces and roles the
      provisioner itself holds. This is checked on `create` and again on
      `rotate_key`.
    - **It cannot create provisioners.** An agent it creates can never be given
      this permission, so a leaked credential cannot copy itself.
    - **It sees only what it created.** Agents made in the dashboard or by
      another provisioner are answered as `not_found`, exactly like agents that
      do not exist.
    - **Policy still applies.** The tenant's agent limit and the organization's
      and tenant's rules apply to every call.

    ## A refused call leaves no agent and no working key


    `create` answers only once the agent's mailbox exists, and the response

    carries its address. A call answered with a refusal has removed the agent,

    and no key from it works, so it is safe to retry. After an

    `internal_error` or a dropped connection, retry too: the retry either

    succeeds or answers `name_taken` naming the agent the first attempt made.


    An address stays reserved after its agent is gone, including one from a

    create interrupted after its mailbox was set up. Asking for it again is

    answered `local_part_taken` without an `agent_id`; choose another.


    An agent must reach something: at least one Bud scope, a namespace, the

    shared `global` namespace or a role. A request for an agent reaching

    nothing is `invalid_request`.


    ## Retrying after a lost response


    Give each agent a `name` derived from your own record of it, such as a

    ticket or customer id. If a response is lost after the call succeeded, the

    retry is refused with `name_taken` and the `agent_id` it already created,

    and because the name is yours that id is the agent you meant. Call

    `rotate_key` with it to get a working key.


    `revoke_key` of a key that is already revoked answers `not_found`.


    ## Errors


    A refusal is a JSON object with `code` and `error`, at the HTTP status the

    code maps to. Branch on the status, then within it on the code. `error` is

    a sentence for a person and may change.


    | code | status | meaning |

    | --- | --- | --- |

    | `invalid_request` | 400 | The body is malformed, a field is wrong, or a
    `before` cursor is not one the API issued. |

    | `unauthorized` | 401 | No credential, or it is unknown, revoked, expired
    or its agent is disabled. |

    | `not_billable` | 402 | The organization cannot create mailboxes until it
    has a payment method. No agent or key from the call remains. |

    | `insufficient_scope` | 403 | The token lacks the `agent:provision` scope.
    |

    | `not_a_provisioner` | 403 | The agent does not have **May create agents
    and keys**, or was itself created by an agent. |

    | `forbidden_by_policy` | 403 | The organization's or tenant's rules forbid
    this agent from creating agents. |

    | `exceeds_provisioner` | 403 | The request asks for more than the
    provisioner itself holds, or, on `rotate_key`, the agent has since been
    given more than that. |

    | `not_found` | 404 | No such agent or key among those this provisioner
    created, or no such operation. |

    | `method_not_allowed` | 405 | The operation exists but only answers `POST`.
    |

    | `agent_limit` | 409 | The tenant holds as many agents as its policy
    allows. |

    | `local_part_taken` | 409 | The address is in use. `agent_id` is set when
    this provisioner created it. |

    | `name_taken` | 409 | The name is in use. `agent_id` is set when this
    provisioner created it. |

    | `conflict` | 409 | Another change to the same agent's key landed first.
    Retry. |

    | `rate_limited` | 429 | A limit was reached. `retry_after_ms`, `scope` and
    `limit` say which and for how long. |

    | `internal_error` | 500 | Something went wrong that is not about the
    request. |

    | `mailbox_unavailable` | 503 | The mailbox could not be set up just now. No
    agent or key from the call remains; retry after the `Retry-After` header's
    seconds, with backoff. |

    | `policy_unavailable` | 503 | Policy could not be evaluated, so the call
    was refused. Retry with backoff. |

    | `temporarily_unavailable` | 503 | The credential could not be checked just
    now. Keep it and retry. |
servers:
  - url: https://access.noetive.io
    description: Noetive Access
security:
  - bearerAuth: []
paths:
  /access/v1/evaluation:
    post:
      summary: Ask whether this agent may take an action
      description: >
        An [OpenID AuthZEN
        1.0](https://openid.net/specs/authorization-api-1_0.html)

        Access Evaluation request, answered by the policy that governs the

        calling agent: the organization's rules, the tenant's, and the agent's

        own, under the roles the agent holds.


        Any agent credential may call it (a key, or an access token for the

        audience `https://access.noetive.io`). It never changes anything: a

        decision is not an approval, and a service that enforces policy itself

        (such as mail) makes its own decision.


        **What you send, and what you cannot.** `subject.id` must be the agent

        the credential belongs to, otherwise the request is `403 forbidden`.

        The subject carries nothing else: who the agent is, its tenant, roles

        and organization come from its credential and are looked up by the

        server. The resource `type` is fixed by the action (see the table

        below) and `id` names it; the server finds its owner and the rest of

        what it needs. Only a `Commitment` accepts `properties`, its `amount`.

        The only `context` you may send is `declared`. A request that supplies

        anything else, such as a role, a recipient flag or an authority mode,

        is `400 invalid_request`: it is refused, never ignored.


        **What the answer means.** `decision` is `true` only when `tier` is

        `allowed`. `tier` is `requires_approval` when a person must approve the

        action first (file it with `approvals/v1`), and `denied` when it can

        never be done as asked. `rule_ids` are the ids of the rules that

        decided and `messages` their explanations, both empty when nothing

        matched (the default is denied). Policy source is never returned.


        What you declare can only describe the request, never relax it: a

        declared classification can raise the sensitivity of a message and not

        lower it, and a rule that forbids the action applies whatever is

        declared. An action decided per recipient (`mail.send`, `mail.send_as`,

        `calendar.attendee.add`) cannot be evaluated here, because recipients

        are known only to the service that sends: it answers `503

        policy_unavailable`.


        | Action | `resource.type` |

        | --- | --- |

        | `mail.read`, `mail.modify`, `mail.delete`, `mailbox.manage`,
        `mail.send`, `mail.send_as` | `Mailbox` (an agent mailbox id) |

        | `attachment.read` | `Attachment` |

        | `calendar.read`, `calendar.freebusy.read`, `calendar.event.create` |
        `Calendar` |

        | `calendar.event.update`, `calendar.event.cancel`,
        `calendar.event.respond`, `calendar.attendee.add` | `CalendarEvent` |

        | `contact.read`, `contact.create` | `AddressBook` |

        | `contact.update`, `contact.delete` | `Contact` |

        | `directory.read`, `directory.export`, `agent.provision` | `Tenant`
        (your own tenant id) |

        | `authority.read`, `authority.modify` | `Authority` |

        | `commitment.create` | `Commitment` |

        | `semantik.publish`, `semantik.search`, `semantik.subscribe` |
        `Namespace` (a namespace id of your tenant) |


        A resource the tenant does not own, or one the server cannot load,

        is answered `denied`.
      operationId: evaluateAccess
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EvaluationRequest'
            example:
              subject:
                type: agent
                id: ag_01jb8x4m2fe0bv3c7y1tq9k2rd
              action:
                name: commitment.create
              resource:
                type: Commitment
                id: po-17
                properties:
                  amount:
                    minor: 1200000
                    currency: SEK
      responses:
        '200':
          description: The decision.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Decision'
              example:
                decision: false
                context:
                  tier: requires_approval
                  rule_ids:
                    - tenant.large_purchase
                  messages:
                    - Purchases over SEK 10000 need approval.
        '400':
          $ref: '#/components/responses/AccessRefusal'
        '401':
          $ref: '#/components/responses/AccessRefusal'
        '403':
          $ref: '#/components/responses/AccessRefusal'
        '404':
          $ref: '#/components/responses/AccessRefusal'
        '405':
          $ref: '#/components/responses/AccessRefusal'
        '500':
          $ref: '#/components/responses/AccessRefusal'
        '503':
          $ref: '#/components/responses/AccessRefusal'
components:
  schemas:
    EvaluationRequest:
      type: object
      additionalProperties: false
      required:
        - subject
        - action
        - resource
      properties:
        subject:
          $ref: '#/components/schemas/Subject'
        action:
          $ref: '#/components/schemas/Action'
        resource:
          $ref: '#/components/schemas/Resource'
        context:
          $ref: '#/components/schemas/EvaluationContext'
    Decision:
      type: object
      required:
        - decision
        - context
      properties:
        decision:
          type: boolean
          description: True only when `context.tier` is `allowed`.
        context:
          type: object
          required:
            - tier
            - rule_ids
            - messages
          properties:
            tier:
              type: string
              enum:
                - allowed
                - requires_approval
                - denied
            rule_ids:
              type: array
              items:
                type: string
              description: The rules that decided, at most 8; empty when nothing matched.
            messages:
              type: array
              items:
                type: string
              description: Their explanations; empty when they have none.
    Subject:
      type: object
      additionalProperties: false
      required:
        - type
        - id
      properties:
        type:
          type: string
          enum:
            - agent
        id:
          type: string
          description: Must be the agent the credential belongs to.
    Action:
      type: object
      additionalProperties: false
      required:
        - name
      properties:
        name:
          type: string
          description: >-
            An action of the vocabulary, such as `mail.send` or
            `commitment.create`.
    Resource:
      type: object
      additionalProperties: false
      required:
        - type
        - id
      properties:
        type:
          type: string
          description: The type the action takes; see the table on `evaluation`.
        id:
          type: string
          minLength: 1
          maxLength: 256
        properties:
          type: object
          additionalProperties: false
          description: Accepted on a `Commitment` only.
          properties:
            amount:
              $ref: '#/components/schemas/Amount'
    EvaluationContext:
      type: object
      additionalProperties: false
      description: Only `declared` may be sent.
      properties:
        declared:
          type: object
          additionalProperties: false
          properties:
            amount:
              $ref: '#/components/schemas/Amount'
            classification:
              type: string
              enum:
                - public
                - internal
                - confidential
                - restricted
              description: Can raise the sensitivity of the request and never lower it.
    AccessRefusal:
      type: object
      required:
        - code
        - error
      properties:
        code:
          type: string
          enum:
            - invalid_request
            - unauthorized
            - forbidden
            - not_found
            - method_not_allowed
            - internal_error
            - policy_unavailable
            - temporarily_unavailable
        error:
          type: string
          description: A sentence for a person. Do not branch on it.
    Amount:
      type: object
      additionalProperties: false
      required:
        - minor
        - currency
      properties:
        minor:
          type: integer
          format: int64
          minimum: 0
          description: Amount in the currency's minor unit (öre, cents).
        currency:
          type: string
          pattern: ^[A-Z]{3}$
          description: ISO 4217 code.
  responses:
    AccessRefusal:
      description: The request was refused. `code` says why.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/AccessRefusal'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: |
        Either a key (`keya_…`) or an access token (a JWT) for the audience
        `https://access.noetive.io`. `agents/v1` needs the scope
        `agent:provision` and an agent with **May create agents and keys**;
        `access/v1` accepts any agent credential. Pass as
        `Authorization: Bearer <credential>`.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.