openapi: 3.1.0
info:
  title: Ricky Developer API
  version: 1.0.0
  description: Organization-scoped REST API for Ricky.
servers:
  - url: https://getricky.ai/api/v1
security:
  - bearerAuth: []
tags:
  - name: Contacts
  - name: Conversations
  - name: Catalogs
  - name: Webhooks
paths:
  /contacts:
    get:
      tags: [Contacts]
      summary: List contacts
      description: 'Scope: contacts:read.'
      parameters:
        - $ref: '#/components/parameters/PageSize'
        - $ref: '#/components/parameters/Cursor'
        - name: query
          in: query
          schema: { type: string, maxLength: 200 }
      responses:
        '200': { $ref: '#/components/responses/ContactPage' }
        default: { $ref: '#/components/responses/Error' }
    post:
      tags: [Contacts]
      summary: Create a contact
      description: 'Scope: contacts:write.'
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ContactWrite' }
      responses:
        '201': { $ref: '#/components/responses/Contact' }
        default: { $ref: '#/components/responses/Error' }
  /contacts/{contactId}:
    parameters: [{ $ref: '#/components/parameters/ContactId' }]
    get:
      tags: [Contacts]
      summary: Get a contact
      description: 'Scope: contacts:read.'
      responses:
        '200': { $ref: '#/components/responses/Contact' }
        default: { $ref: '#/components/responses/Error' }
    patch:
      tags: [Contacts]
      summary: Update a contact
      description: 'Scope: contacts:write. Omitted detail fields are preserved.'
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [details]
              additionalProperties: false
              properties:
                details: { $ref: '#/components/schemas/ContactDetails' }
                customValues: { type: object, additionalProperties: true }
      responses:
        '200': { $ref: '#/components/responses/Contact' }
        default: { $ref: '#/components/responses/Error' }
  /conversations:
    get:
      tags: [Conversations]
      summary: List conversations
      description: 'Scope: conversations:read.'
      parameters:
        - $ref: '#/components/parameters/PageSize'
        - $ref: '#/components/parameters/Cursor'
        - name: status
          in: query
          schema: { type: string, enum: [all, open, closed], default: all }
        - name: assigned_user_id
          in: query
          schema: { type: string, format: uuid }
      responses:
        '200':
          description: A page of conversations.
          content:
            application/json:
              schema:
                type: object
                required: [data, pagination]
                properties:
                  data: { type: array, items: { $ref: '#/components/schemas/Conversation' } }
                  pagination:
                    type: object
                    properties:
                      next_cursor: { type: [string, 'null'] }
        default: { $ref: '#/components/responses/Error' }
  /conversations/{conversationId}:
    parameters: [{ $ref: '#/components/parameters/ConversationId' }]
    get:
      tags: [Conversations]
      summary: Get a conversation
      description: 'Scope: conversations:read.'
      responses:
        '200': { $ref: '#/components/responses/Conversation' }
        default: { $ref: '#/components/responses/Error' }
  /conversations/{conversationId}/messages:
    parameters: [{ $ref: '#/components/parameters/ConversationId' }]
    get:
      tags: [Conversations]
      summary: List conversation timeline items
      description: 'Scope: conversations:read.'
      parameters: [{ $ref: '#/components/parameters/Cursor' }]
      responses:
        '200':
          description: A page of timeline items.
          content:
            application/json:
              schema:
                type: object
                required: [data, pagination]
                properties:
                  data: { type: array, items: { $ref: '#/components/schemas/TimelineItem' } }
                  pagination:
                    type: object
                    properties:
                      next_cursor: { type: [string, 'null'] }
        default: { $ref: '#/components/responses/Error' }
    post:
      tags: [Conversations]
      summary: Queue a text message
      description: 'Scope: messages:send. 202 means accepted, not delivered.'
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [text]
              additionalProperties: false
              properties:
                text: { type: string, minLength: 1, maxLength: 10000 }
                message_id: { type: string, format: uuid }
      responses:
        '202': { $ref: '#/components/responses/Data' }
        default: { $ref: '#/components/responses/Error' }
  /conversations/{conversationId}/assignment:
    parameters: [{ $ref: '#/components/parameters/ConversationId' }]
    patch:
      tags: [Conversations]
      summary: Assign or unassign a conversation
      description: 'Scope: conversations:write.'
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [assigned_user_id]
              additionalProperties: false
              properties:
                assigned_user_id: { type: [string, 'null'], format: uuid }
                sync_contact_owner: { type: boolean, default: true }
      responses:
        '200': { $ref: '#/components/responses/Data' }
        default: { $ref: '#/components/responses/Error' }
  /conversations/{conversationId}/close:
    parameters: [{ $ref: '#/components/parameters/ConversationId' }]
    post:
      tags: [Conversations]
      summary: Close a conversation
      description: 'Scope: conversations:write.'
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                category:
                  { type: [string, 'null'], enum: [resolved, sale, no_response, spam, other, null] }
      responses:
        '200': { $ref: '#/components/responses/Data' }
        default: { $ref: '#/components/responses/Error' }
  /conversations/{conversationId}/reopen:
    parameters: [{ $ref: '#/components/parameters/ConversationId' }]
    post:
      tags: [Conversations]
      summary: Reopen a conversation
      description: 'Scope: conversations:write.'
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      responses:
        '200': { $ref: '#/components/responses/Data' }
        default: { $ref: '#/components/responses/Error' }
  /members:
    get:
      tags: [Catalogs]
      summary: List organization members
      description: 'Scope: members:read.'
      responses:
        '200': { $ref: '#/components/responses/Data' }
        default: { $ref: '#/components/responses/Error' }
  /tags:
    get:
      tags: [Catalogs]
      summary: List contact tags
      description: 'Scope: tags:read.'
      responses:
        '200': { $ref: '#/components/responses/Data' }
        default: { $ref: '#/components/responses/Error' }
  /templates:
    get:
      tags: [Catalogs]
      summary: List WhatsApp templates
      description: 'Scope: templates:read.'
      responses:
        '200': { $ref: '#/components/responses/Data' }
        default: { $ref: '#/components/responses/Error' }
  /webhooks:
    get:
      tags: [Webhooks]
      summary: List webhook subscriptions
      description: 'Scope: webhooks:manage.'
      responses:
        '200': { $ref: '#/components/responses/Data' }
        default: { $ref: '#/components/responses/Error' }
    post:
      tags: [Webhooks]
      summary: Create a webhook subscription
      description: 'Scope: webhooks:manage. The signing_secret is returned only on creation.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url, event_types]
              additionalProperties: false
              properties:
                url: { type: string, format: uri, maxLength: 2048 }
                event_types:
                  type: array
                  minItems: 1
                  maxItems: 5
                  items:
                    type: string
                    enum:
                      [
                        message.received,
                        message.sent,
                        conversation.opened,
                        conversation.closed,
                        conversation.assignment_changed,
                      ]
      responses:
        '201': { $ref: '#/components/responses/Data' }
        default: { $ref: '#/components/responses/Error' }
  /webhooks/{webhookId}:
    parameters: [{ $ref: '#/components/parameters/WebhookId' }]
    delete:
      tags: [Webhooks]
      summary: Disable a webhook subscription
      description: 'Scope: webhooks:manage.'
      responses:
        '200': { $ref: '#/components/responses/Data' }
        default: { $ref: '#/components/responses/Error' }
  /webhooks/{webhookId}/deliveries:
    parameters: [{ $ref: '#/components/parameters/WebhookId' }]
    get:
      tags: [Webhooks]
      summary: List the 100 most recent deliveries
      description: 'Scope: webhooks:manage.'
      responses:
        '200': { $ref: '#/components/responses/Data' }
        default: { $ref: '#/components/responses/Error' }
  /webhooks/{webhookId}/deliveries/{deliveryId}/retry:
    parameters:
      - $ref: '#/components/parameters/WebhookId'
      - name: deliveryId
        in: path
        required: true
        schema: { type: string, format: uuid }
    post:
      tags: [Webhooks]
      summary: Retry a failed delivery
      description: 'Scope: webhooks:manage.'
      responses:
        '200': { $ref: '#/components/responses/Data' }
        default: { $ref: '#/components/responses/Error' }
components:
  securitySchemes:
    bearerAuth: { type: http, scheme: bearer }
  parameters:
    PageSize:
      name: page_size
      in: query
      schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
    Cursor:
      name: cursor
      in: query
      schema: { type: string }
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema: { type: string, minLength: 8, maxLength: 200 }
    ContactId:
      name: contactId
      in: path
      required: true
      schema: { type: string, format: uuid }
    ConversationId:
      name: conversationId
      in: path
      required: true
      schema: { type: string, format: uuid }
    WebhookId:
      name: webhookId
      in: path
      required: true
      schema: { type: string, format: uuid }
  responses:
    Error:
      description: API error. Possible status codes include 400, 401, 403, 404, 409, 413, 415, 422, 429, and 500.
      content:
        application/json:
          schema:
            type: object
            required: [error]
            properties:
              error:
                type: object
                required: [code, message]
                properties:
                  code: { type: string }
                  message: { type: string }
    Data:
      description: Resource data.
      content:
        application/json:
          schema:
            type: object
            required: [data]
            properties:
              data: { type: object, additionalProperties: true }
    Contact:
      description: One contact.
      content:
        application/json:
          schema:
            type: object
            required: [data]
            properties:
              data: { $ref: '#/components/schemas/Contact' }
    ContactPage:
      description: A page of contacts.
      content:
        application/json:
          schema:
            type: object
            required: [data]
            properties:
              data:
                type: object
                required: [items, next_cursor, total_count]
                properties:
                  items: { type: array, items: { $ref: '#/components/schemas/Contact' } }
                  next_cursor: { type: [string, 'null'] }
                  total_count: { type: integer }
    Conversation:
      description: One conversation.
      content:
        application/json:
          schema:
            type: object
            required: [data]
            properties:
              data: { $ref: '#/components/schemas/Conversation' }
  schemas:
    ContactDetails:
      type: object
      additionalProperties: false
      properties:
        firstName: { type: [string, 'null'], maxLength: 5000 }
        lastName: { type: [string, 'null'], maxLength: 5000 }
        primaryEmail: { type: [string, 'null'], format: email }
        primaryPhone: { type: [string, 'null'], maxLength: 5000 }
        company: { type: [string, 'null'], maxLength: 5000 }
        jobTitle: { type: [string, 'null'], maxLength: 5000 }
        notes: { type: [string, 'null'], maxLength: 5000 }
        ownerUserId: { type: [string, 'null'], format: uuid }
        leadStatus: { type: [string, 'null'] }
        lifecycleStage: { type: [string, 'null'] }
        city: { type: [string, 'null'] }
        country: { type: [string, 'null'] }
        region: { type: [string, 'null'] }
        streetAddress: { type: [string, 'null'] }
        postalCode: { type: [string, 'null'] }
        birthday: { type: [string, 'null'] }
        websiteUrl: { type: [string, 'null'] }
        linkedinUrl: { type: [string, 'null'] }
        source: { type: [string, 'null'] }
    ContactWrite:
      type: object
      required: [details]
      additionalProperties: false
      properties:
        details: { $ref: '#/components/schemas/ContactDetails' }
        customValues: { type: object, additionalProperties: true }
        tagIds: { type: array, maxItems: 30, items: { type: string, format: uuid } }
    Contact:
      type: object
      required:
        [id, first_name, last_name, primary_email, primary_phone, tags, identities, updated_at]
      properties:
        id: { type: string, format: uuid }
        first_name: { type: [string, 'null'] }
        last_name: { type: [string, 'null'] }
        primary_email: { type: [string, 'null'] }
        primary_phone: { type: [string, 'null'] }
        tags: { type: array, items: { type: object, additionalProperties: true } }
        identities: { type: array, items: { type: object, additionalProperties: true } }
        custom_values: { type: object, additionalProperties: true }
        updated_at: { type: string, format: date-time }
      additionalProperties: true
    Conversation:
      type: object
      required: [id, channel, status, created_at]
      properties:
        id: { type: string, format: uuid }
        channel:
          type: object
          properties:
            id: { type: [string, 'null'], format: uuid }
            type: { type: string, enum: [whatsapp, instagram, messenger] }
        contact: { type: [object, 'null'], additionalProperties: true }
        assigned_user_id: { type: [string, 'null'], format: uuid }
        status: { type: string, enum: [open, closed] }
        created_at: { type: string, format: date-time }
      additionalProperties: true
    TimelineItem:
      type: object
      required: [id, kind, direction, created_at]
      properties:
        id: { type: string, format: uuid }
        kind: { type: string }
        direction: { type: [string, 'null'] }
        content: { type: [string, 'null'] }
        created_at: { type: string, format: date-time }
      additionalProperties: true
