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

> ## Agent Instructions
> The Relay API base URL is https://api.relayapp.im. Never use workers.dev origins.
> The contract is raw HTTPS and JSON at https://api.relayapp.im. The one optional published package is @relaymessenger/cli. Import nothing else.
> One send is one message. Mint a message_id (msg_ plus a lowercase Crockford ULID) before sending; it is the message's canonical id and the send's idempotency key, so a retry with the same id replays the stored message.
> Message content is immutable. There is no edit, unsend, or delete route, and no message versions or tombstones.
> A reply is a pointer: reply_to is { message_id, part_id? } and the client draws the quote from the target.
> Verify webhooks with the Standard Webhooks signature over the exact raw request body before parsing it.
> Webhooks and GET /v1/events read the same durable log and can run at once. The pull is plain: after is the last sequence you processed, and nothing is acknowledged.
> An agent in a group is an ordinary member. It receives every message from the sequence it joined at; there are no invocations and no invocation_id.

# Create an agent and mint its token

> Creates an agent owned by the signed-in person and returns its Agent Token. This is the only response that carries the token; store it now. A terminal bridge calls this once, with the session it received from the device flow, and then uses the token for everything else.




## OpenAPI

````yaml /api-reference/openapi.yaml post /v1/me/agents
openapi: 3.1.0
info:
  title: Relay developer API
  version: '0.1'
  description: >
    Add Relay as a channel for your agent, receive messages, and reply with
    plain HTTPS and JSON. Authenticate every request with an Agent Token unless
    the endpoint is marked otherwise. Data parts are stored and delivered as
    sent and render through their fallback text, except for the reserved
    `data.type` values Relay resolves itself.
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
servers:
  - url: https://api.relayapp.im
    description: Production
security:
  - agentToken: []
tags:
  - name: Agent
    description: Inspect the agent controlled by the current Agent Token.
  - name: Messages
    description: Send a message, read history, and react.
  - name: Conversations
    description: >
      List the conversations an agent is in, advance receipts, and show the
      typing indicator.
  - name: Events
    description: >
      Receive durable inbound events, either through signed webhook receivers or
      by pulling `GET /v1/events`. Both work at once: one durable log per agent
      feeds both transports. Delivery is at least once everywhere; always
      deduplicate by `event_id`.
  - name: Attachments
    description: Upload bytes once and reference them from any number of parts.
  - name: Pairing
    description: >
      Device authorization (RFC 8628) for terminal bridges. Four calls, in this
      order: the computer asks for a code, the person opens the verification URI
      so the code is claimed for their account, the person approves it, and the
      computer exchanges the code for a session it uses to provision its agent
      and mint that agent's token. The token never travels to the phone.
  - name: Public
    description: Share profiles.
paths:
  /v1/me/agents:
    post:
      tags:
        - Pairing
      summary: Create an agent and mint its token
      description: >
        Creates an agent owned by the signed-in person and returns its Agent
        Token. This is the only response that carries the token; store it now. A
        terminal bridge calls this once, with the session it received from the
        device flow, and then uses the token for everything else.
      operationId: createOwnedAgent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateAgentInput'
      responses:
        '201':
          description: Agent created; store the token now.
          content:
            application/json:
              schema:
                type: object
                required:
                  - agent
                  - token
                  - chat_id
                properties:
                  agent:
                    $ref: '#/components/schemas/Agent'
                  token:
                    type: string
                    pattern: ^rly_live_
                    description: The Agent Token. Returned once and never again.
                  chat_id:
                    type: string
                    pattern: ^cnv_
                    description: The owner's direct conversation with the new agent.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: >
            `handle_taken`: the handle is in use. `handle_reserved`: the handle
            belongs to Relay.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: >-
            An unsupported field is present, or the handle, name, tagline, or
            opening message is invalid.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/WriteBudgetExceeded'
      security:
        - userSession: []
components:
  schemas:
    CreateAgentInput:
      type: object
      additionalProperties: false
      required:
        - handle
        - displayName
      description: >
        Camel-cased, like the `Agent` projection it creates. Any field outside
        this list answers 422 rather than being ignored.
      properties:
        handle:
          type: string
          pattern: ^[a-z][a-z0-9_]{2,31}$
          description: >
            The agent's name, unique only among this creator's agents.
            Lowercased on write. Reserved names answer 409, and a dot answers
            422: the dot joins a name to its owner and belongs to neither half.
          example: scheduler
        displayName:
          type: string
          minLength: 1
          maxLength: 80
        tagline:
          type: string
          maxLength: 120
        avatarUrl:
          type:
            - string
            - 'null'
          format: uri
          maxLength: 2048
          pattern: ^https://(?![^/?#]*@)
        accentColor:
          type:
            - string
            - 'null'
          example: '#0B75FF'
        openingMessage:
          type:
            - object
            - 'null'
          description: >
            The message the agent sends when someone installs it, as a `parts`
            array. Null for no opening message.
          properties:
            parts:
              type: array
              minItems: 1
              items:
                $ref: '#/components/schemas/PartInput'
    Agent:
      type: object
      description: >
        Consumer-safe agent identity. Top-level keys are camelCase here and the
        nested `creator` object is snake_case, which is the shape the app reads;
        every other body in this API is snake_case throughout. Installation is
        present only on an installed-agent projection. A system prompt,
        provider, model, runtime, credential, or backend configuration is never
        included.
      additionalProperties: false
      required:
        - id
        - handle
        - displayName
        - tagline
        - visibility
        - status
        - createdAt
      properties:
        id:
          type: string
          pattern: ^agt_
          example: agt_01JZRELAY
        handle:
          type: string
          pattern: ^[a-z][a-z0-9_]{2,31}(\.[a-z][a-z0-9_]{2,31})?$
          description: >
            The address people type. A system agent and a brand's front-door
            agent wear a flat name; every other agent wears its creator's, as
            name.creator. It is derived when the agent is read, so renaming a
            creator changes it with no other call.
          example: relay
        displayName:
          type: string
          example: Relay
        tagline:
          type: string
          example: Your built-in Relay agent
        avatarUrl:
          type: string
          format: uri
          maxLength: 2048
          pattern: ^https://(?![^/?#]*@)
        accentColor:
          type: string
          example: '#0B75FF'
        visibility:
          type: string
          enum:
            - private
            - unlisted
            - public
        installation:
          $ref: '#/components/schemas/AgentInstallation'
        creator:
          $ref: '#/components/schemas/AgentCreator'
        preinstalled:
          type: boolean
          description: >
            Present only on a built-in agent that arrives already installed.
            Every other agent omits the field.
        removable:
          type: boolean
          description: >
            Present only on an agent a user may not remove. Every other agent
            omits the field, and `installation.canRemove` carries the same state
            on an installed projection.
        status:
          type: string
          enum:
            - active
            - suspended
            - retired
        createdAt:
          type: string
          format: date-time
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              example: invalid_request
            message:
              type: string
              example: chat_id is required
    PartInput:
      oneOf:
        - $ref: '#/components/schemas/TextPartInput'
        - $ref: '#/components/schemas/MediaPartInput'
        - $ref: '#/components/schemas/LinkPreviewPartInput'
        - $ref: '#/components/schemas/DataPartInput'
      discriminator:
        propertyName: type
    AgentInstallation:
      type: object
      description: >
        User-agent messaging relationship, not a runtime connection. A false
        canRemove hides and rejects ordinary removal; Block and Report remain
        available as safety overrides.
      additionalProperties: false
      required:
        - id
        - state
        - conversationId
        - canRemove
      properties:
        id:
          type: string
          pattern: ^ins_
        state:
          type: string
          enum:
            - installed
            - removed
        conversationId:
          type: string
          pattern: ^cnv_
        canRemove:
          type: boolean
    AgentCreator:
      type: object
      description: >
        Who is accountable for the agent, as the app shows it under Creator on
        the profile. `kind` is `organization` for a business creator and
        `person` for an account that made the agent in the app; a person's
        `display_name` and `avatar_url` ride along only on a public agent.
        Organization values reflect the creator's live record, so a rename or a
        new logo reaches every one of that creator's agents without a
        resubmission. Sub-keys are snake_case.
      additionalProperties: false
      required:
        - id
        - name
        - verified
      properties:
        id:
          type: string
          example: org_01JZRELAY
        name:
          type: string
          example: Relay
        kind:
          type: string
          enum:
            - person
            - organization
          description: Absent on older servers, which clients must read as `organization`.
        display_name:
          type: string
          description: Person creators on a public agent only.
        avatar_url:
          type: string
          format: uri
          maxLength: 2048
          pattern: ^https://(?![^/?#]*@)
          description: Person creators on a public agent only.
        domain:
          type: string
          description: >
            The creator's verified domain. Omitted when the creator has not
            verified one.
          example: relayapp.im
        logo_url:
          type: string
          format: uri
          maxLength: 2048
          pattern: ^https://(?![^/?#]*@)
          description: Square creator mark. Omitted when the creator has set none.
        support_url:
          type: string
          format: uri
          maxLength: 2048
          pattern: ^https://(?![^/?#]*@)
          description: >
            Where a user reaches the creator about this agent. Omitted when the
            creator has set none.
        verified:
          type: boolean
          description: >
            True when Relay has verified the creator's domain and the creator is
            not suspended. A person creator is never verified.
    TextPartInput:
      type: object
      additionalProperties: false
      required:
        - type
        - text
      properties:
        type:
          type: string
          const: text
        text:
          type: string
          minLength: 1
          description: UTF-8 text, limited to 8 KB by the server.
        mention:
          $ref: '#/components/schemas/Mention'
        mention_range:
          type: array
          minItems: 2
          maxItems: 2
          items:
            type: integer
            minimum: 0
          description: >
            [start, end) UTF-16 code-unit offsets over `text`: one mention per
            text part, carried the way Linq carries them. The ranged text is
            display text and is not required to spell the handle. Required when
            `mention` is present. A mention notifies the named participant even
            when they have muted the group.
        styles:
          type: array
          maxItems: 200
          description: >
            Formatting runs over `text`. Each range covers a run of UTF-16 code
            units, sorted by `start` and non-overlapping, and names the formats
            applied to that run. Styles are presentation only: the text stays
            canonical without them, and fallback text ignores them. An empty
            array is meaningful and kept. It marks the part as structured plain
            text, distinguishing it from a legacy Markdown body. Omit the field
            entirely for a legacy body.
          items:
            $ref: '#/components/schemas/StyleRange'
    MediaPartInput:
      type: object
      additionalProperties: false
      required:
        - type
      properties:
        type:
          type: string
          const: media
        url:
          type: string
          format: uri
          pattern: ^https://
          description: >
            Public HTTPS source URL. The hostname must be publicly reachable:
            private and reserved ranges, loopback, and IP-literal hostnames are
            rejected. At commit the server imports the bytes into Relay-owned
            attachment storage and rewrites the part to reference the copy, so
            delivered history never depends on the third-party host staying
            alive.
        attachment_id:
          type: string
          pattern: ^att_
          description: Available attachment created by `POST /v1/attachments`.
        content_type:
          type: string
          pattern: ^[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]*/[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]*$
          example: video/mp4
          description: >
            MIME type for a public URL. For attachment_id sources, Relay uses
            the MIME type stored at upload and rejects a conflicting value.
        media_kind:
          type: string
          enum:
            - image
            - video
            - audio
            - file
          description: >
            Optional presentation intent. Relay derives this from a specific
            image/*, video/*, or audio/* content_type. Use it to identify
            generic application/octet-stream media.
        filename:
          type: string
          minLength: 1
          maxLength: 180
          example: quarterly-report.pdf
          description: >
            Optional display filename, shown on a file row. Control characters
            are rejected and surrounding whitespace is trimmed. For
            attachment_id sources Relay uses the filename stored at upload and
            ignores this value.
        size_bytes:
          type: integer
          minimum: 0
          maximum: 104857600
          example: 2418562
          description: >
            Optional byte count, shown beside the filename so a reader sees the
            size before downloading. For attachment_id sources Relay uses the
            stored byte count and ignores this value.
        width:
          type: integer
          minimum: 1
          maximum: 100000
          description: >
            Optional pixel width, provided together with height. Clients use the
            pair to reserve the true aspect ratio before the media bytes
            download. When omitted for an uploaded image attachment, the server
            derives both values from the stored bytes.
        height:
          type: integer
          minimum: 1
          maximum: 100000
          description: Optional pixel height, provided together with width.
        blur_hash:
          type: string
          minLength: 6
          maxLength: 96
          pattern: ^[0-9A-Za-z#$%*+,\-.:;=?@\[\]^_{|}~]{6,96}$
          description: >
            Optional blurhash placeholder (base83 characters only, see
            https://blurha.sh). Clients decode it into a soft preview before the
            media bytes download. When omitted for an uploaded image attachment,
            the server derives one from the stored bytes at commit.
      dependentRequired:
        width:
          - height
        height:
          - width
      oneOf:
        - properties:
            url: {}
          required:
            - url
        - properties:
            attachment_id: {}
          required:
            - attachment_id
    LinkPreviewPartInput:
      type: object
      additionalProperties: false
      required:
        - type
        - url
      description: URL rendered as a native rich preview when metadata is available.
      properties:
        type:
          type: string
          const: link
        url:
          type: string
          format: uri
          pattern: ^https://
          maxLength: 2048
        title:
          type: string
          maxLength: 512
          description: >
            Optional page title you resolved for this URL. Relay carries the
            sender's metadata with the message rather than fetching the page for
            each recipient, the way Signal carries an OWSLinkPreviewDraft and
            iMessage carries an LPLinkMetadata, so no recipient opens a
            connection to a URL somebody else chose. Send it with the message:
            Relay never backfills it later.
        description:
          type: string
          maxLength: 512
          description: >
            Optional page description you resolved for this URL, carried with
            the same rules as title.
    DataPartInput:
      type: object
      additionalProperties: false
      required:
        - type
        - data
      properties:
        type:
          type: string
          const: data
        data:
          description: >
            Integration-defined JSON, limited to 16 KB by the server. Relay
            stores and delivers it as sent; clients render a part-level fallback
            string when present, then the message text. Consumers must ignore
            unknown fields inside a data part.

            One `data.type` value is reserved and is not carried through as
            sent: `agent_card` names an agent to share and Relay resolves the
            card itself, so what you send is AgentCardDataInput and what
            recipients receive is AgentCardData.
          anyOf:
            - $ref: '#/components/schemas/AgentCardDataInput'
            - description: Other integration-defined JSON data.
    Mention:
      type: string
      pattern: ^[a-z][a-z0-9_]{2,31}(\.[a-z][a-z0-9_]{2,31})?$
      description: >
        The address of a mentioned user or agent, without the leading "@". A
        person, a system agent and a brand's front-door agent carry a flat name;
        every other agent carries its creator's, as name.creator. The text
        `mention_range` covers is display text and does not have to spell it.
    StyleRange:
      type: object
      additionalProperties: false
      required:
        - start
        - length
        - styles
      properties:
        start:
          type: integer
          minimum: 0
          description: UTF-16 code-unit offset of the styled run in `text`.
        length:
          type: integer
          minimum: 1
          description: Length of the styled run in UTF-16 code units.
        styles:
          type: array
          minItems: 1
          items:
            type: string
            enum:
              - bold
              - italic
              - underline
              - strikethrough
          description: >
            Formats applied to the run: the four every reference messenger
            ships. The server deduplicates the names and stores them in this
            enum's order, so equal formatting always serializes identically. Any
            other name answers 422.
    AgentCardDataInput:
      type: object
      additionalProperties: false
      required:
        - type
        - handle
      description: >
        The `data` value of a data part that shares a Relay agent. A sender
        names the agent and nothing else: every field the card draws is read
        from the agent record inside the same transaction that commits the
        message, so a card cannot dress one agent in another's identity. Any
        property beyond `type` and `handle` returns 422, as does a handle that
        names no agent or names a retired one.
      properties:
        type:
          type: string
          const: agent_card
        handle:
          type: string
          description: Relay agent handle, without the "@". Lowercased on commit.
          example: relay
  responses:
    Unauthorized:
      description: The token or session is absent or invalid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    WriteBudgetExceeded:
      description: >
        The caller spent its budget for this kind of write. Each budgeted route
        holds two buckets, one per caller and a looser one per network address,
        and the tighter of the two answers first. The body carries
        `rate_limited`. See [Limits](/reference/limits) for the ceilings.
      headers:
        Retry-After:
          description: Whole seconds until the exhausted bucket admits another write.
          schema:
            type: integer
            minimum: 1
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    agentToken:
      type: http
      scheme: bearer
      description: Agent Token (`rly_live_…`), shown once when the agent is created.
    userSession:
      type: http
      scheme: bearer
      description: >-
        Better Auth user-session bearer used by the Relay app and by a paired
        bridge; never an Agent Token.

````