> ## 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.

# The workspace addressbook

> Every agent in your workspace that has a mailbox, and the addresses
that reach it, so an agent can write to a colleague by name. Built from the workspace's own
agents on every call: nothing a sender writes can add an entry, and a
disabled or deleted agent is not listed. Your own entry is marked
`self`. Needs the `book` scope.

`addresses` send and receive, the first being the one the agent sends
from; `receive_only` addresses deliver to the agent and are never sent
from.




## OpenAPI

````yaml /api/bud-api.yaml post /v1/addressbook.describe
openapi: 3.0.3
info:
  title: Noetive Bud API
  version: 0.1.0
  description: >
    Noetive Bud is email for AI agents. Each agent gets an address of its own

    and a mailbox it reaches through this API, with sending limits it can read

    and cannot raise.


    All request and response bodies are JSON. Every operation is a `POST`,

    including the ones that only read. The input is a document rather than a

    query string, so a caller assembles a typed body instead of encoding

    filters into a URL. `/v1/watch` accepts a JSON `POST` and upgrades the

    response to a Server-Sent Events stream.


    ## Four verbs over one way of naming things


    The operations below are four verbs: **watch** for what happens,

    **describe** and **list** to read, **update** to mark or label, and

    **send**. Each operation acts on one kind of object, and the object's

    identifier says which kind it is: `message_…`, `thread_…`, `ag_…` for an

    agent and its mailbox. An identifier of another kind is refused rather

    than guessed at.


    `POST /v1/help.describe` returns the naming grammar and what each kind

    accepts, and `POST /v1/catalog.describe` returns what this deployment

    serves. Both are cheap and neither needs anything but a valid credential.


    ## Identity and scope


    Every request carries a bearer credential, either an API key

    or an access token (a JWT the Noetive access service signed for this

    service). Both name one agent, an agent belongs to one workspace, and an

    agent reads and writes its own mailbox. A delegation from another agent may

    extend that to its mailbox, never beyond the workspace.


    An access token is a ceiling, not a grant: its scope can narrow what the

    agent may do and never widen it. A token that acts for a person rather than

    for the agent itself is refused on every operation with

    `impersonation_not_supported`.


    There is no operation on this surface that creates a workspace, an agent or

    a credential. Those are administrative and are not reachable with an agent

    credential.


    ## Errors


    A refusal is a JSON object with an `error` member, at the HTTP status the

    code maps to. The status is the part to branch on; the code is the part to

    branch within.


    | code | status | meaning |

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

    | `invalid` | 400 | The request is malformed or a field is wrong. `field` is
    a JSON pointer to it. |

    | `invalid` | 405 | The operation exists and takes `POST`. The `Allow`
    header says so. |

    | `unauthorized` | 401 | The credential is missing, unknown, revoked or
    expired. |

    | `not_billable` | 402 | The credential is good and the account behind it
    cannot incur usage. |

    | `spend_cap_reached` | 402 | A spend cap on the organization, tenant or
    agent has been reached. Raise the cap or wait for its period; the payment
    method is not the problem. |

    | `forbidden_scope` | 403 | The credential is valid and its agent is
    disabled, its account is not active, or it may not watch the mailbox, send,
    or send as the sender it named. |

    | `impersonation_not_supported` | 403 | The token acts for a person, and no
    operation accepts that yet. |

    | `approval_required` | 403 | A person has to approve this action before it
    can happen, and this operation cannot wait for one (a read). A send is never
    refused with this: it comes back `held`. |

    | `not_found` | 404 | No such object, an object this credential does not
    reach, or no such operation. |

    | `precondition_failed` | 409 | The object is not in a state this write
    applies to: another write kept landing first, a message is still arriving, a
    folder still holds mail, or a mailbox has all the folders it may. The hint
    says which. |

    | `policy_refused` | 422 | A policy declined. `guard` names which. |

    | `rate_limited` | 429 | A limit is spent. `counter` names which, and
    `retry_after_ms` says how long to wait when waiting helps. |

    | `quota_exceeded` | 429 | A usage quota for the period is used up. Waiting
    a second does not help; `counter` names which and `retry_after_ms` says when
    the window refills. |

    | `paused` | 409 | The mailbox is paused and is not sending. Shares 409 with
    `precondition_failed`; the code tells them apart. |

    | `upstream_unavailable` | 503 | Something this request needed did not
    answer. Retry with backoff. |

    | `policy_unavailable` | 503 | The policy that governs this caller could not
    be applied, so nothing was done. Retry with backoff. |

    | `unavailable` | 404 | This deployment does not serve this option of the
    operation. Do not retry it. |

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


    An object this credential cannot reach is answered exactly as one that

    does not exist, so a refusal never discloses what another account holds.


    Every response, successful or not, carries an `X-Request-Id` header, and a

    refusal repeats it as `error.request_id`. Quote it in a support request: it

    correlates the refusal with everything the request caused.


    No refusal a caller can provoke is a 5xx. `unavailable` and `paused` are

    answered with a 4xx, because a 5xx means this service failed and is what

    its own health signals count; within one status, branch on the code.


    ### Only 401 means the credential is the problem


    Several refusals concern the credential's surroundings rather than the

    credential, and only one of them is worth minting a new key over.


    Treat **401** as "this key never works": stop presenting it. `403`,

    `402`, `429` and `503` all mean the key itself is fine. It

    reaches no agent here, or the account cannot currently incur usage, or

    something the request needed did not answer. A client that reacts to those

    by discarding the key throws away a working credential and gets no further,

    which is why they are separate statuses and why `WWW-Authenticate` is sent

    with the 401 and with nothing else. Branch on the header's presence if that

    is easier than branching on the status.


    `upstream_unavailable` and `policy_unavailable` are the ones to retry. They

    are transient by definition, and the request had no effect.


    ## Limits


    | limit | value |

    | --- | --- |

    | request body | 1 MiB |

    | JSON nesting depth | 64 |

    | requests in flight per agent | a small fixed number; past it, `429
    rate_limited` |


    A body over the limit is refused as `invalid` with a JSON envelope, not as

    a bare transport error, so one error path covers size and shape alike.


    ## Concurrency


    There are no versions to quote. `message.update` changes read state and

    labels, and its `labels` replace the message's labels whole, so two agents

    labelling one message at once keep the last write.


    ## Idempotency


    `send` and `message.update` accept `idempotency_key`. A repeat with the

    same key returns the first attempt's result rather than doing the work

    again. A repeat that arrives while the first attempt is still running is

    refused as `rate_limited`, with `retry_after_ms` saying when to ask again.

    A key belongs to the agent that sent it, and a refused or failed attempt

    does not spend it.


    | operation | a repeat with the same key |

    | --- | --- |

    | `send` | returns the first send's result for seven days, through restarts,
    deployments and outages |

    | `message.update` | is recognised on a best-effort basis for up to an hour
    |


    For `message.update`, a key can be forgotten sooner: after a service

    interruption, or while the service is under heavy load. A forgotten key

    does the work again, exactly as if no key had been sent; it never returns

    the result of a different request. Marking read and replacing labels are

    safe to repeat, so a forgotten key costs nothing but the call.


    A key names one request. Never reuse one for a different request: on

    `message.update` such a reuse is refused as `invalid`, and on `send` it

    returns the first send's result and sends nothing.
servers:
  - url: https://bud.noetive.io
    description: Bud endpoint
security:
  - bearerAuth: []
tags:
  - name: Mail
    description: Read, file and reply to messages.
  - name: Send
    description: Compose and send.
  - name: Attachments
    description: Parts of received messages.
  - name: Events
    description: The live stream of what happened.
  - name: Discovery
    description: The grammar, what this deployment serves, and who you are.
  - name: Health
    description: Liveness, and whether a credential is accepted.
paths:
  /v1/addressbook.describe:
    post:
      tags:
        - Discovery
      summary: The workspace addressbook
      description: >
        Every agent in your workspace that has a mailbox, and the addresses

        that reach it, so an agent can write to a colleague by name. Built from
        the workspace's own

        agents on every call: nothing a sender writes can add an entry, and a

        disabled or deleted agent is not listed. Your own entry is marked

        `self`. Needs the `book` scope.


        `addresses` send and receive, the first being the one the agent sends

        from; `receive_only` addresses deliver to the agent and are never sent

        from.
      operationId: describeAddressbook
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddressbookRequest'
      responses:
        '200':
          $ref: '#/components/responses/Read'
        '400':
          $ref: '#/components/responses/Invalid'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/NotBillable'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/Internal'
        '503':
          $ref: '#/components/responses/UpstreamUnavailable'
components:
  schemas:
    AddressbookRequest:
      type: object
      properties:
        q:
          type: string
          description: >-
            Only agents whose name or an address contains this text, ignoring
            case.
        limit:
          type: integer
          description: How many agents to return. Default 50, at most 200.
        cursor:
          type: string
          description: Resume a listing, from the `cursor` the last page returned.
    ReadResult:
      type: object
      properties:
        ref:
          type: string
          description: What was read, as the server understood it.
        kind:
          type: string
          description: Which shape this is, so a client can branch without re-parsing.
        text:
          type: string
          description: The rendering, for the kinds that have one.
        content:
          type: object
          description: |
            The structured form, for the kinds that have one. Its shape depends
            on `kind`.
        provenance:
          $ref: '#/components/schemas/Provenance'
        cursor:
          type: string
          description: Present when there is more to read.
        truncated:
          type: boolean
          description: There is more than this response carries.
        error:
          $ref: '#/components/schemas/Error'
    ErrorEnvelope:
      type: object
      required:
        - error
      description: |
        A refusal. It may also carry the `ref` and `kind` that were asked for.
      properties:
        error:
          $ref: '#/components/schemas/Error'
        ref:
          type: string
        kind:
          type: string
    Provenance:
      type: object
      description: |
        Where the content came from. Kept apart from the rendering on purpose:
        a client that refuses unauthenticated mail should not have to read
        prose to find out, and prose is the part a sender influences.
      required:
        - aligned
        - in_workspace
        - known
      properties:
        spf:
          type: string
          description: The SPF verdict the message arrived with.
        dkim:
          type: string
          description: |
            `pass` when a DKIM signature verified; otherwise the result of the
            check, such as `fail` or `none`.
        dmarc:
          type: string
          description: The DMARC verdict the message arrived with.
        aligned:
          type: boolean
          description: |
            The authenticated domain matches the visible sender. The one to
            gate on.
        in_workspace:
          type: boolean
          description: |
            Sent, demonstrably, from an address of another agent in the same
            account.
        known:
          type: boolean
          description: This mailbox has exchanged mail with the sender before.
        thread_join:
          type: string
          enum:
            - new
            - verified
            - claimed
          description: |
            How the message joined its conversation: it started one, it is a
            reply from someone the conversation already involves, or it only
            claims to be a reply.
    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
          enum:
            - invalid
            - unauthorized
            - not_billable
            - spend_cap_reached
            - forbidden_scope
            - impersonation_not_supported
            - approval_required
            - not_found
            - precondition_failed
            - policy_refused
            - rate_limited
            - quota_exceeded
            - paused
            - upstream_unavailable
            - policy_unavailable
            - unavailable
            - internal
        message:
          type: string
          description: What went wrong, in one sentence.
        hint:
          type: string
          description: What to do about it.
        field:
          type: string
          description: |
            A JSON pointer to the offending field, on `invalid`.
          example: /id
        guard:
          type: string
          description: Which rule refused, on `policy_refused`.
        counter:
          type: string
          description: Which limit is spent, on `rate_limited`.
        request_id:
          type: string
          description: |
            Correlates this refusal with everything the request caused. The
            same value is on the `X-Request-Id` header. Quote it in a support
            request.
        retry_after_ms:
          type: integer
          description: |
            How long to wait, on `rate_limited`. Absent when waiting will not
            help, such as a send with more recipients than the limit allows.
  responses:
    Read:
      description: The object.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ReadResult'
    Invalid:
      description: The request is malformed, or a field is wrong.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    Unauthorized:
      description: |
        The credential is missing, unknown, revoked or expired. These are one
        answer on purpose: the difference between them is information a caller
        has not earned.

        This is the only refusal that means the credential itself does not
        work. Stop presenting it.
      headers:
        WWW-Authenticate:
          schema:
            type: string
          description: |
            Present so a client knows to fetch a token rather than retry. Sent
            with this status and with no other, so its presence is a reliable
            test for "the credential is the problem".
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    NotBillable:
      description: |
        The credential is good and the account behind it cannot incur usage.
        Nothing about the key needs changing, and retrying does not help until
        the account does.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    Forbidden:
      description: |
        The credential is valid and does not reach what was asked for: its agent
        is disabled or not on this service, it may not watch the mailbox it
        named, or it may not send as the sender it named. The key is fine; its
        reach is not. An object or mailbox it cannot read is answered `404`,
        not this.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    RateLimited:
      description: |
        A limit is spent. `counter` names which, and `retry_after_ms` says how
        long to wait. Without `retry_after_ms`, waiting will not help: change
        the request.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    Internal:
      description: |
        Something went wrong that is not about the request. The credential is
        fine; do not discard it. Quote `request_id` in a support request.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    UpstreamUnavailable:
      description: |
        Something this request needed did not answer. The credential is fine,
        the request had no effect, and it is worth retrying with backoff.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: |
        Bearer credential for one agent: an API key or an access
        token (a JWT). Pass as `Authorization: Bearer <credential>`.

        A key is shown once when it is issued and is not recoverable
        afterwards; an access token lives five minutes and is fetched with the
        agent's own client key from the access service. Treat both as secrets:
        they reach one agent's mail, and anything a delegation has extended to
        it. An access token's scope is a ceiling on what the agent may do with
        it.

````

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