---
openapi: 3.0.3
info:
  title: Velents Platform API
  description: |-
    Combined API reference for the Velents platform, covering two surfaces:

    **1. Agent.sa API** — Programmatically manage AI agent conversations. Initiate outbound WhatsApp or Voice conversations (individually or in bulk), retrieve agent configuration, and receive real-time lifecycle events via webhooks.

    **2. Support Center API** — Tenant-authenticated REST API for managing support tickets and conversations from external client systems (CRMs, e-commerce backends, internal tools, etc.). Create tickets, read ticket state, append messages, and retrieve conversation history.

    ---

    ## Authentication

    Both APIs share the same base URL and use Bearer token authentication:

    ```
    Authorization: Bearer {token}
    ```

    - **Agent.sa tokens** are generated from the Agent.sa platform dashboard — one token per agent.
    - **Support Center tokens** follow the format `AppName_tenantId_PersonalAccessToken_plainTextToken` and are issued by your Velents account administrator. Each token carries a set of *abilities*; the required ability for each endpoint is noted in the endpoint description.

    ---

    ## Webhooks (Agent.sa)

    When using the Dispatch or Batch endpoints you may supply a `webhook.link` URL. The platform will POST JSON events to that URL as the conversation progresses.

    ### Event: `onStarted`
    Fired once at the very beginning of a conversation.
    ```json
    {
      "event": { "type": "onStarted", "about": "conversation" },
      "agent": { "id": 4, "public": "boddy_rahsaan_Agent_4" },
      "conversation": {
        "id": 4,
        "phone": "+201145803442",
        "values": { "company_name": "acme", "order_id": "1212" },
        "created_at": "2025-11-13 23:24:50"
      },
      "result": { "extraction": [] }
    }
    ```

    ### Event: `onMessage`
    Fired every time there is a new exchange of messages.
    ```json
    {
      "event": { "type": "onMessage", "about": "conversation" },
      "agent": { "id": 4, "public": "boddy_rahsaan_Agent_4" },
      "conversation": { "id": 4, "phone": "+201145803442", "values": { "company_name": "acme", "order_id": "1212" } },
      "result": {
        "extraction": [],
        "message": {
          "customer": "the date is today nightly 10 clock",
          "agent": "ممتاز! تبي تحجز اليوم الساعة 10 بالليل...",
          "timestamp": "2025-11-13T23:25:10Z"
        }
      }
    }
    ```

    ### Event: `onUpdate`
    Fired when the agent successfully extracts new information.
    ```json
    {
      "event": { "type": "onUpdate", "about": "conversation" },
      "agent": { "id": 4, "public": "boddy_rahsaan_Agent_4" },
      "conversation": { "id": 4, "phone": "+201145803442", "values": { "company_name": "acme", "order_id": "1212" } },
      "result": {
        "extraction": { "customer_name": "shady" },
        "message": { "customer": "my name is shady", "agent": "حياك يا Shady! ...", "timestamp": "2025-11-13T23:25:48Z" },
        "history": [ { "role": "agent", "message": "مرحبا! أنا مساعد acme...", "timestamp": "..." } ]
      }
    }
    ```

    ### Event: `onEnded`
    Fired once when the conversation has concluded.
    ```json
    {
      "event": { "type": "onEnded", "about": "conversation" },
      "agent": { "id": 4, "public": "boddy_rahsaan_Agent_4" },
      "conversation": { "id": 4, "phone": "+201145803442", "values": { "company_name": "acme", "order_id": "1212" } },
      "result": {
        "extraction": { "customer_name": "shady" },
        "history": [ { "role": "agent", "message": "مرحبا! أنا مساعد acme...", "timestamp": "..." } ]
      }
    }
    ```

    ---

    ## Conventions (Support Center)

    ### Successful responses
    All successful responses wrap the payload under a `data` key:
    ```json
    { "data": { ... } }
    ```
    Lists return `data` as an array with optional `meta` and `links` for pagination.

    ### Error responses
    | Status | Meaning |
    |--------|---------|
    | 400 | Malformed request |
    | 401 | Missing or invalid bearer token |
    | 403 | Token lacks required ability |
    | 404 | Resource not found or not visible to this tenant |
    | 422 | Validation error — see `errors` map |
    | 429 | Rate limit exceeded — see `Retry-After` header |
    | 5xx | Server error — safe to retry with backoff |

    ### Validation error shape
    ```json
    {
      "message": "The given data was invalid.",
      "errors": {
        "subject": ["The subject field is required."]
      }
    }
    ```

    ### Rate limits
    Per-token, per-minute limits. Headers returned on every response:
    ```
    X-RateLimit-Limit: 30
    X-RateLimit-Remaining: 29
    Retry-After: 17   # only on 429
    ```

    ---

    ## Typical Support Center Flow

    1. `GET /Company/TenantAuth/me` — confirm token and capture tenant ID.
    2. `POST /Company/Support/Tickets` — create a ticket when a new case opens; save `public_id` and `conversation_id`.
    3. `POST /Company/Conversation/{Conversation}/Send` — push subsequent customer replies into the conversation.
    4. `GET /Company/Conversation/{Conversation}/history` (or a webhook) — surface agent replies back into your system.
  version: 1.2.1
servers:
- url: https://velents-agents.velents.ai
  description: Production Server
tags:
- name: Agent
  description: Retrieve information and configuration for your Agent.sa agent.
- name: Outbound Actions
  description: Initiate outbound conversations via WhatsApp or Voice (Agent.sa).
- name: Tenant
  description: Verify and inspect the tenant identity behind a Support Center token.
- name: Support Tickets
  description: Create and read support tickets on behalf of your end customers.
- name: Conversations
  description: Append messages to, and retrieve history from, ticket conversations.
paths:
  "/Integration/Agent":
    get:
      tags:
      - Agent
      summary: Get Agent Details
      description: Fetches the primary details associated with the agent linked to
        your API token.
      security:
      - agentBearerAuth: []
      responses:
        '200':
          description: Agent details retrieved successfully.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/AgentDetailsResponse"
        '401':
          description: Missing or invalid bearer token.
  "/Integration/Agent/Values":
    get:
      tags:
      - Agent
      summary: Get Agent Value Keys
      description: Retrieves the predefined keys (variables) that your agent is configured
        to use in conversations.
      security:
      - agentBearerAuth: []
      responses:
        '200':
          description: Agent value keys retrieved successfully.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/AgentValueKeysResponse"
        '401':
          description: Missing or invalid bearer token.
  "/Integration/Agent/CsvFile":
    get:
      tags:
      - Agent
      summary: Download CSV Batch File Template
      description: Downloads a pre-formatted CSV template to use with the Batch endpoint.
      security:
      - agentBearerAuth: []
      responses:
        '200':
          description: CSV template file returned.
          content:
            text/csv:
              schema:
                type: string
                format: binary
        '401':
          description: Missing or invalid bearer token.
  "/Integration/Agent/Batch":
    post:
      tags:
      - Outbound Actions
      summary: Create a Batch of Contacts
      description: |-
        Initiates outbound conversations for a large list of contacts at once.

        **Channels:** `channel_id: 2` = WhatsApp, `channel_id: 3` = Voice.
      security:
      - agentBearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/BatchRequest"
      responses:
        '202':
          description: Batch accepted for processing.
        '401':
          description: Missing or invalid bearer token.
        '422':
          description: Validation error.
  "/Integration/Agent/Dispatch":
    post:
      tags:
      - Outbound Actions
      summary: Dispatch a Single Action
      description: |-
        Initiates a conversation with a single contact immediately.

        **Channels:** `channel_id: 2` = WhatsApp, `channel_id: 3` = Voice.
      security:
      - agentBearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/DispatchRequest"
      responses:
        '202':
          description: Dispatch accepted for processing.
        '401':
          description: Missing or invalid bearer token.
        '422':
          description: Validation error.
  "/Company/TenantAuth/me":
    get:
      tags:
      - Tenant
      summary: Get Tenant Identity
      description: |-
        Returns details about the tenant that owns the bearer token. Use to verify credentials at startup or to discover the tenant ID for downstream calls.

        **Required ability:** `Tenant-Show`
      security:
      - supportBearerAuth: []
      responses:
        '200':
          description: Tenant details retrieved successfully.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/TenantResponse"
        '401':
          description: Missing or invalid bearer token.
        '403':
          description: Token lacks the `Tenant-Show` ability.
  "/Company/Support/Tickets":
    post:
      tags:
      - Support Tickets
      summary: Create Ticket
      description: |-
        Creates a new support ticket and its associated conversation in a single call. New tickets start in the `human_pending` state. Persist both `public_id` and `conversation_id` from the response — you will need them to read the ticket and send messages.

        **Required ability:** `Tickets-Create`

        **Rate limit:** 30 requests / minute
      security:
      - supportBearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/CreateTicketRequest"
      responses:
        '201':
          description: Ticket created successfully.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/TicketResponse"
        '401':
          description: Missing or invalid bearer token.
        '403':
          description: Token lacks the `Tickets-Create` ability.
        '422':
          "$ref": "#/components/responses/ValidationError"
        '429':
          "$ref": "#/components/responses/RateLimitExceeded"
    get:
      tags:
      - Support Tickets
      summary: List Tickets
      description: |-
        Returns tickets visible to the authenticated tenant, newest first. Results are paginated.

        **Required ability:** `Tickets-List`

        **Rate limit:** 40 requests / minute
      security:
      - supportBearerAuth: []
      responses:
        '200':
          description: Paginated list of tickets.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/TicketListResponse"
        '401':
          description: Missing or invalid bearer token.
        '403':
          description: Token lacks the `Tickets-List` ability.
        '429':
          "$ref": "#/components/responses/RateLimitExceeded"
  "/Company/Support/Tickets/{public_id}":
    get:
      tags:
      - Support Tickets
      summary: Show Ticket
      description: |-
        Fetches a single ticket by its `public_id`. Returns the same shape as a list entry plus extended fields (assignee, category, tags, conversation reference).

        **Required ability:** `Tickets-Show`

        **Rate limit:** 40 requests / minute
      security:
      - supportBearerAuth: []
      parameters:
      - name: public_id
        in: path
        required: true
        description: The `public_id` returned by the Create Ticket endpoint (e.g.
          `tkt_01HX...`).
        schema:
          type: string
          example: tkt_01HX...
      responses:
        '200':
          description: Ticket details retrieved successfully.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/TicketDetailResponse"
        '401':
          description: Missing or invalid bearer token.
        '403':
          description: Token lacks the `Tickets-Show` ability.
        '404':
          description: Ticket not found or not visible to this tenant.
        '429':
          "$ref": "#/components/responses/RateLimitExceeded"
  "/Company/Conversation/{Conversation}/Send":
    post:
      tags:
      - Conversations
      summary: Send Message
      description: |-
        Appends a plain-text message to a conversation. The `conversation_id` is returned by Create Ticket and is also exposed on the ticket resource itself.

        **Required ability:** `Conversations-Send`

        **Rate limit:** 60 requests / minute
      security:
      - supportBearerAuth: []
      parameters:
      - name: Conversation
        in: path
        required: true
        description: The conversation ID (e.g. `cnv_01HX...`).
        schema:
          type: string
          example: cnv_01HX...
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/SendMessageRequest"
      responses:
        '200':
          description: Message appended successfully.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/MessageResponse"
        '401':
          description: Missing or invalid bearer token.
        '403':
          description: Token lacks the `Conversations-Send` ability.
        '404':
          description: Conversation not found.
        '422':
          "$ref": "#/components/responses/ValidationError"
        '429':
          "$ref": "#/components/responses/RateLimitExceeded"
  "/Company/Conversation/{Conversation}/history":
    get:
      tags:
      - Conversations
      summary: Get Conversation History
      description: |-
        Returns the full message history of a conversation in chronological order.

        **Required ability:** `Tickets-History`

        **Rate limit:** 40 requests / minute
      security:
      - supportBearerAuth: []
      parameters:
      - name: Conversation
        in: path
        required: true
        description: The conversation ID (e.g. `cnv_01HX...`).
        schema:
          type: string
          example: cnv_01HX...
      responses:
        '200':
          description: Conversation history retrieved successfully.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ConversationHistoryResponse"
        '401':
          description: Missing or invalid bearer token.
        '403':
          description: Token lacks the `Tickets-History` ability.
        '404':
          description: Conversation not found.
        '429':
          "$ref": "#/components/responses/RateLimitExceeded"
components:
  securitySchemes:
    agentBearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 'Agent.sa API token. Generate from the Agent.sa platform dashboard
        — one token per agent. Include as `Authorization: Bearer YOUR_AGENT_TOKEN`.'
    supportBearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 'Support Center tenant token. Format: `AppName_tenantId_PersonalAccessToken_plainTextToken`.
        Issued by your Velents account administrator. Each token carries a set of
        abilities; endpoints return 403 if the required ability is absent. Include
        as `Authorization: Bearer YOUR_TOKEN`.'
  responses:
    ValidationError:
      description: Validation error — see `errors` map.
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/ValidationErrorResponse"
    RateLimitExceeded:
      description: Rate limit exceeded. Retry after the number of seconds in the `Retry-After`
        header.
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds to wait before retrying.
  schemas:
    WebhookObject:
      type: object
      description: Optional webhook configuration for Agent.sa outbound actions.
      properties:
        link:
          type: string
          format: uri
          description: The URL that will receive POST event payloads.
        body:
          type: object
          additionalProperties: true
          description: Additional key-value pairs merged into every webhook payload.
        headers:
          type: object
          additionalProperties:
            type: string
          description: Custom HTTP headers sent with every webhook request.
    BatchRequest:
      type: object
      required:
      - channel_id
      - name
      - rows
      properties:
        channel_id:
          type: integer
          description: 2 = WhatsApp, 3 = Voice.
          example: 2
        name:
          type: string
          description: Human-readable label for this batch.
          example: Q4 Marketing Campaign
        rows:
          type: array
          description: List of contacts to contact.
          items:
            type: object
            properties:
              phone:
                type: string
                description: E.164 phone number.
                example: "+15551234567"
              values:
                type: object
                additionalProperties: true
                description: Key-value pairs injected into the agent's conversation
                  context.
        webhook:
          "$ref": "#/components/schemas/WebhookObject"
    DispatchRequest:
      type: object
      required:
      - channel_id
      - phone
      properties:
        channel_id:
          type: integer
          description: 2 = WhatsApp, 3 = Voice.
          example: 2
        phone:
          type: string
          description: E.164 phone number of the contact.
          example: "+15551234567"
        values:
          type: object
          additionalProperties: true
          description: Key-value pairs injected into the agent's conversation context.
          example:
            company_name: Your Company
            order_id: TXN-12345
        webhook:
          "$ref": "#/components/schemas/WebhookObject"
    AgentDetailsResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: integer
              example: 4
            public:
              type: string
              example: boddy_rahsaan_Agent_4
            name:
              type: string
            channel_ids:
              type: array
              items:
                type: integer
    AgentValueKeysResponse:
      type: object
      properties:
        data:
          type: array
          items:
            type: string
          example:
          - company_name
          - order_id
          - customer_name
    CreateTicketRequest:
      type: object
      required:
      - subject
      properties:
        subject:
          type: string
          description: Short summary of the issue.
          example: New support ticket
        description:
          type: string
          description: Long-form details. Becomes the first message in the conversation.
          example: Ticket created via API
        identifier:
          type: string
          description: Your external reference (e.g. order ID).
          example: ORD-12345
        priority:
          type: string
          enum:
          - low
          - normal
          - high
          - urgent
          default: normal
          description: Ticket priority level.
        channel:
          type: string
          description: Source channel. Defaults to `unknown`.
          example: api
        category_id:
          type: integer
          description: ID of an existing ticket category.
        tags:
          type: array
          items:
            type: string
          description: Free-form tags applied to the ticket.
          example:
          - billing
          - urgent
    TicketResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            public_id:
              type: string
              description: Unique public identifier for the ticket. Use in path parameters.
              example: tkt_01HX...
            subject:
              type: string
              example: New support ticket
            status:
              type: string
              example: human_pending
              description: Lifecycle state of the ticket.
            priority:
              type: string
              example: normal
            identifier:
              type: string
              example: ORD-12345
              description: Your external reference, if provided.
            conversation_id:
              type: string
              description: ID of the associated conversation. Use to send/read messages.
              example: cnv_01HX...
            created_at:
              type: string
              format: date-time
              example: '2026-05-06T10:14:00Z'
    TicketListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            "$ref": "#/components/schemas/TicketResponse/properties/data"
        meta:
          type: object
          properties:
            current_page:
              type: integer
              example: 1
            per_page:
              type: integer
              example: 25
            total:
              type: integer
              example: 1
    TicketDetailResponse:
      type: object
      description: Same shape as TicketResponse plus extended fields.
      properties:
        data:
          allOf:
          - "$ref": "#/components/schemas/TicketResponse/properties/data"
          - type: object
            properties:
              assignee:
                type: object
                nullable: true
              category:
                type: object
                nullable: true
              tags:
                type: array
                items:
                  type: string
    TenantResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: integer
              example: 42
            name:
              type: string
              example: Acme Corp
            subdomain:
              type: string
              example: acme
            abilities:
              type: array
              items:
                type: string
              example:
              - Tenant-Show
              - Tickets-Create
              - Tickets-List
              - Tickets-Show
              - Tickets-History
              - Conversations-Send
    SendMessageRequest:
      type: object
      required:
      - message
      properties:
        message:
          type: string
          description: The plain-text message body.
          example: Hello, I need help with my order
    MessageResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
              example: msg_01HX...
            conversation_id:
              type: string
              example: cnv_01HX...
            body:
              type: string
              example: Hello, I need help with my order
            created_at:
              type: string
              format: date-time
              example: '2026-05-06T10:21:09Z'
    ConversationHistoryResponse:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                example: msg_01HX...
              author:
                type: object
                properties:
                  type:
                    type: string
                    enum:
                    - customer
                    - agent
                    example: customer
                  name:
                    type: string
                    example: Acme Corp
              body:
                type: string
                example: Ticket created via API
              created_at:
                type: string
                format: date-time
                example: '2026-05-06T10:14:00Z'
    ValidationErrorResponse:
      type: object
      properties:
        message:
          type: string
          example: The given data was invalid.
        errors:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          example:
            subject:
            - The subject field is required.
security:
- agentBearerAuth: []
- supportBearerAuth: []
