openapi: 3.1.0
info:
  title: Runvello API
  version: 1.0.0
  description: >-
    Tenant-scoped API for approved Runvello integrations. Use the workspace host that
    issued the bearer token. Contracts, campaigns, invoices, projects, and tasks are
    read-only. Legacy operations are retained only for compatibility.
  contact:
    name: Runvello Support
    email: support@runvello.com
servers:
  - url: https://{workspace}.runvello.com
    description: Runvello workspace host
    variables:
      workspace:
        default: your-workspace
        description: Workspace subdomain
security:
  - bearerAuth: []
x-runvello-scopes:
  bookings:read: View bookings and scheduling details.
  bookings:write: Create and update bookings.
  clients:read: View clients.
  clients:write: Create and update clients.
  clients:unbilled_time_entries:read: View client unbilled time.
  contracts:read: View contracts.
  email_campaigns:read: View email campaigns.
  invoices:read: View invoices.
  leads:read: View leads.
  leads:write: Create and update leads.
  leads:convert: Preview and perform lead conversion.
  people:read: View people.
  people:write: Create and update people.
  projects:read: View projects.
  tasks:read: View project and time-entry tasks.
tags:
  - { name: CRM, description: 'Clients, contacts, and leads.' }
  - { name: Scheduling, description: Workspace bookings. }
  - { name: Work, description: Projects and tasks. }
  - { name: Finance, description: 'Contracts, invoices, and unbilled time.' }
  - { name: Marketing, description: Email campaign reporting. }
x-common-read-errors: &common_read_errors
  '401': { $ref: '#/components/responses/Unauthorized' }
  '403': { $ref: '#/components/responses/Forbidden' }
  '429': { $ref: '#/components/responses/RateLimited' }
  '503': { $ref: '#/components/responses/ServiceUnavailable' }
x-common-member-errors: &common_member_errors
  <<: *common_read_errors
  '404': { $ref: '#/components/responses/NotFound' }
x-common-write-errors: &common_write_errors
  <<: *common_member_errors
  '400': { $ref: '#/components/responses/BadRequest' }
  '422': { $ref: '#/components/responses/ValidationFailed' }
paths:
  /api/v1/clients:
    get:
      operationId: listClients
      x-required-scope: clients:read
      tags: [CRM]
      summary: List clients
      description: Returns tenant-scoped clients in most-recently-updated order.
      parameters:
        - { $ref: '#/components/parameters/Page' }
        - { $ref: '#/components/parameters/PerPage' }
        - { $ref: '#/components/parameters/Search' }
        - { name: status, in: query, description: Filter by client lifecycle status., schema: { $ref: '#/components/schemas/ClientStatus' } }
        - { name: client_type, in: query, description: Filter by individual or company., schema: { $ref: '#/components/schemas/ClientType' } }
      responses:
        '200': { $ref: '#/components/responses/ClientsCollection' }
        <<: *common_read_errors
    post:
      operationId: createClient
      x-required-scope: clients:write
      tags: [CRM]
      summary: Create a client
      description: Creates a client inside the token workspace and enforces the current plan client limit.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ClientWriteEnvelope' }
            examples: { company: { value: { client: { name: Acme Studio, client_type: company, email: hello@example.com, status: active } } } }
      responses:
        '201': { $ref: '#/components/responses/ClientRecord' }
        <<: *common_write_errors
  /api/v1/clients/{id}:
    parameters: [ { $ref: '#/components/parameters/Id' } ]
    get:
      operationId: getClient
      x-required-scope: clients:read
      tags: [CRM]
      summary: Get a client
      description: Returns one client from the token workspace.
      responses:
        '200': { $ref: '#/components/responses/ClientRecord' }
        <<: *common_member_errors
    patch:
      operationId: updateClient
      x-required-scope: clients:write
      tags: [CRM]
      summary: Update a client
      description: Updates allowlisted profile fields; organization and ownership boundaries cannot be bypassed.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ClientWriteEnvelope' }
            examples: { status: { value: { client: { status: paused, phone: '+1 555 0100' } } } }
      responses:
        '200': { $ref: '#/components/responses/ClientRecord' }
        <<: *common_write_errors
  /api/v1/clients/{client_id}/people:
    parameters: [ { $ref: '#/components/parameters/ClientId' } ]
    get:
      operationId: listClientPeople
      x-required-scope: people:read
      tags: [CRM]
      summary: List a client's people
      description: Returns contacts belonging to the selected tenant-scoped client.
      parameters: [ { $ref: '#/components/parameters/Page' }, { $ref: '#/components/parameters/PerPage' }, { $ref: '#/components/parameters/Search' } ]
      responses:
        '200': { $ref: '#/components/responses/PeopleCollection' }
        <<: *common_member_errors
    post:
      operationId: createClientPerson
      x-required-scope: people:write
      tags: [CRM]
      summary: Create a person for a client
      description: Creates a contact bound to the selected client; client ownership cannot be supplied in the body.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PersonWriteEnvelope' }
            examples: { contact: { value: { person: { full_name: Taylor Jordan, email: taylor@example.com, phone_number: '+1 555 0101' } } } }
      responses:
        '201': { $ref: '#/components/responses/PersonRecord' }
        <<: *common_write_errors
  /api/v1/people:
    get:
      operationId: listPeople
      x-required-scope: people:read
      tags: [CRM]
      summary: List people
      description: Returns tenant-scoped contacts, optionally narrowed to one client.
      parameters:
        - { $ref: '#/components/parameters/Page' }
        - { $ref: '#/components/parameters/PerPage' }
        - { $ref: '#/components/parameters/Search' }
        - { name: client_id, in: query, description: Filter by client ID., schema: { type: integer, format: int64, minimum: 1 } }
      responses:
        '200': { $ref: '#/components/responses/PeopleCollection' }
        <<: *common_read_errors
  /api/v1/people/{id}:
    parameters: [ { $ref: '#/components/parameters/Id' } ]
    get:
      operationId: getPerson
      x-required-scope: people:read
      tags: [CRM]
      summary: Get a person
      description: Returns one tenant-scoped client contact.
      responses:
        '200': { $ref: '#/components/responses/PersonRecord' }
        <<: *common_member_errors
    patch:
      operationId: updatePerson
      x-required-scope: people:write
      tags: [CRM]
      summary: Update a person
      description: Updates name, email, or phone; moving a contact between clients is not allowed.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PersonWriteEnvelope' }
            examples: { rename: { value: { person: { full_name: Taylor J. } } } }
      responses:
        '200': { $ref: '#/components/responses/PersonRecord' }
        <<: *common_write_errors
  /api/v1/leads:
    get:
      operationId: listLeads
      x-required-scope: leads:read
      tags: [CRM]
      summary: List leads
      description: Returns tenant-scoped leads ordered by most recent update.
      parameters:
        - { $ref: '#/components/parameters/Page' }
        - { $ref: '#/components/parameters/PerPage' }
        - { $ref: '#/components/parameters/Search' }
        - { name: stage, in: query, description: Filter by pipeline stage., schema: { $ref: '#/components/schemas/LeadStage' } }
        - { name: lead_intent, in: query, description: Filter by lead intent., schema: { $ref: '#/components/schemas/LeadIntent' } }
        - { name: conversion_target, in: query, description: Filter by conversion target., schema: { $ref: '#/components/schemas/ConversionTarget' } }
      responses:
        '200': { $ref: '#/components/responses/LeadsCollection' }
        <<: *common_read_errors
    post:
      operationId: createLead
      x-required-scope: leads:write
      tags: [CRM]
      summary: Create a lead
      description: Creates a tenant-scoped lead using allowlisted CRM fields.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/LeadWriteEnvelope' }
            examples: { lead: { value: { lead: { full_name: Morgan Lee, email: morgan@example.com, stage: new, lead_intent: service_customer } } } }
      responses:
        '201': { $ref: '#/components/responses/LeadRecord' }
        <<: *common_write_errors
  /api/v1/leads/{id}:
    parameters: [ { $ref: '#/components/parameters/Id' } ]
    get:
      operationId: getLead
      x-required-scope: leads:read
      tags: [CRM]
      summary: Get a lead
      description: Returns one tenant-scoped lead.
      responses:
        '200': { $ref: '#/components/responses/LeadRecord' }
        <<: *common_member_errors
    patch:
      operationId: updateLead
      x-required-scope: leads:write
      tags: [CRM]
      summary: Update a lead
      description: Updates allowlisted CRM fields and validates any owner against workspace membership.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/LeadWriteEnvelope' }
            examples: { qualify: { value: { lead: { stage: qualified } } } }
      responses:
        '200': { $ref: '#/components/responses/LeadRecord' }
        <<: *common_write_errors
  /api/v1/leads/{id}/convert-preview:
    parameters: [ { $ref: '#/components/parameters/Id' } ]
    get:
      operationId: previewLeadConversion
      x-required-scope: leads:convert
      tags: [CRM]
      summary: Preview lead conversion
      description: Validates conversion defaults and warnings without mutating the lead.
      parameters:
        - { name: 'conversion[conversion_target]', in: query, schema: { $ref: '#/components/schemas/ConversionTarget' } }
        - { name: 'conversion[client_name]', in: query, schema: { type: string } }
        - { name: 'conversion[client_type]', in: query, schema: { $ref: '#/components/schemas/ClientType' } }
        - { name: 'conversion[invite_user]', in: query, description: Include invite-related validation warnings., schema: { type: boolean } }
        - { name: 'conversion[organization_name]', in: query, description: Override the organization name in an organization-account preview., schema: { type: string } }
        - { name: 'conversion[subdomain]', in: query, description: Override the proposed subdomain in an organization-account preview., schema: { type: string, pattern: '^[a-z0-9][a-z0-9-]*$' } }
      responses:
        '200': { $ref: '#/components/responses/LeadConversionPreview' }
        <<: *common_member_errors
  /api/v1/leads/{id}/convert:
    parameters: [ { $ref: '#/components/parameters/Id' } ]
    post:
      operationId: convertLead
      x-required-scope: leads:convert
      tags: [CRM]
      summary: Convert a lead
      description: Converts a lead into a tenant client or an invited organization account.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/LeadConversionEnvelope' }
            examples: { client: { value: { conversion: { conversion_target: tenant_client, client_name: Morgan Lee, client_type: individual, invite_user: false } } } }
      responses:
        '201': { $ref: '#/components/responses/LeadConversionResult' }
        <<: *common_write_errors
  /api/v1/bookings:
    get:
      operationId: listBookings
      x-required-scope: bookings:read
      tags: [Scheduling]
      summary: List bookings
      description: Returns tenant-scoped bookings in chronological order.
      parameters:
        - { $ref: '#/components/parameters/Page' }
        - { $ref: '#/components/parameters/PerPage' }
        - { name: status, in: query, description: Filter by booking status., schema: { $ref: '#/components/schemas/BookingStatus' } }
        - { name: payment_status, in: query, description: Filter by payment status., schema: { $ref: '#/components/schemas/BookingPaymentStatus' } }
        - { name: client_id, in: query, schema: { type: integer, format: int64, minimum: 1 } }
        - { name: booking_service_id, in: query, schema: { type: integer, format: int64, minimum: 1 } }
      responses:
        '200': { $ref: '#/components/responses/BookingsCollection' }
        <<: *common_read_errors
    post:
      operationId: createBooking
      x-required-scope: bookings:write
      tags: [Scheduling]
      summary: Create a booking
      description: Creates a booking through the normal workflow with tenant relationship and availability validation.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/BookingWriteEnvelope' }
            examples: { requested: { value: { booking: { booking_service_id: 4, client_id: 12, user_id: 7, starts_at: '2026-08-01T15:00:00Z', status: requested, time_zone: America/Chicago } } } }
      responses:
        '201': { $ref: '#/components/responses/BookingRecord' }
        <<: *common_write_errors
  /api/v1/bookings/{id}:
    parameters: [ { $ref: '#/components/parameters/Id' } ]
    get:
      operationId: getBooking
      x-required-scope: bookings:read
      tags: [Scheduling]
      summary: Get a booking
      description: Returns one tenant-scoped booking.
      responses:
        '200': { $ref: '#/components/responses/BookingRecord' }
        <<: *common_member_errors
    patch:
      operationId: updateBooking
      x-required-scope: bookings:write
      tags: [Scheduling]
      summary: Update a booking
      description: Updates a booking through the normal status, notification, and automation workflow.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/BookingWriteEnvelope' }
            examples: { confirm: { value: { booking: { status: confirmed, location: Studio A } } } }
      responses:
        '200': { $ref: '#/components/responses/BookingRecord' }
        <<: *common_write_errors
  /api/v1/projects:
    get:
      operationId: listProjects
      x-required-scope: projects:read
      tags: [Work]
      summary: List projects
      description: Returns tenant-scoped projects. This resource is read-only.
      parameters:
        - { $ref: '#/components/parameters/Page' }
        - { $ref: '#/components/parameters/PerPage' }
        - { name: client_id, in: query, schema: { type: integer, format: int64, minimum: 1 } }
        - { name: status, in: query, schema: { $ref: '#/components/schemas/ProjectStatus' } }
      responses:
        '200': { $ref: '#/components/responses/ProjectsCollection' }
        <<: *common_read_errors
  /api/v1/project-tasks:
    get:
      operationId: listProjectTasks
      x-required-scope: tasks:read
      tags: [Work]
      summary: List project tasks
      description: Returns project and standalone tasks. This resource is read-only.
      parameters:
        - { $ref: '#/components/parameters/Page' }
        - { $ref: '#/components/parameters/PerPage' }
        - { name: status, in: query, schema: { $ref: '#/components/schemas/TaskStatus' } }
        - { name: project_id, in: query, schema: { type: integer, format: int64, minimum: 1 } }
        - { name: assigned_user_id, in: query, schema: { type: integer, format: int64, minimum: 1 } }
        - { name: client_id, in: query, schema: { type: integer, format: int64, minimum: 1 } }
      responses:
        '200': { $ref: '#/components/responses/TasksCollection' }
        <<: *common_read_errors
  /api/v1/contracts:
    get:
      operationId: listContracts
      x-required-scope: contracts:read
      tags: [Finance]
      summary: List contracts
      description: Returns tenant-scoped contracts without private generation metadata. Read-only.
      parameters:
        - { $ref: '#/components/parameters/Page' }
        - { $ref: '#/components/parameters/PerPage' }
        - { name: status, in: query, schema: { $ref: '#/components/schemas/ContractStatus' } }
        - { name: client_id, in: query, schema: { type: integer, format: int64, minimum: 1 } }
        - { name: project_id, in: query, schema: { type: integer, format: int64, minimum: 1 } }
      responses:
        '200': { $ref: '#/components/responses/ContractsCollection' }
        <<: *common_read_errors
  /api/v1/contracts/{id}:
    parameters: [ { $ref: '#/components/parameters/Id' } ]
    get:
      operationId: getContract
      x-required-scope: contracts:read
      tags: [Finance]
      summary: Get a contract
      description: Returns one tenant-scoped contract. Read-only.
      responses:
        '200': { $ref: '#/components/responses/ContractRecord' }
        <<: *common_member_errors
  /api/v1/invoices:
    get:
      operationId: listInvoices
      x-required-scope: invoices:read
      tags: [Finance]
      summary: List invoices
      description: Returns invoices with line items and payments. Read-only.
      parameters:
        - { $ref: '#/components/parameters/Page' }
        - { $ref: '#/components/parameters/PerPage' }
        - { $ref: '#/components/parameters/Search' }
        - { name: status, in: query, schema: { $ref: '#/components/schemas/InvoiceStatus' } }
        - { name: client_id, in: query, schema: { type: integer, format: int64, minimum: 1 } }
        - { name: stripe_status, in: query, description: Exact Stripe invoice status., schema: { type: string } }
      responses:
        '200': { $ref: '#/components/responses/InvoicesCollection' }
        <<: *common_read_errors
  /api/v1/invoices/{id}:
    parameters: [ { $ref: '#/components/parameters/Id' } ]
    get:
      operationId: getInvoice
      x-required-scope: invoices:read
      tags: [Finance]
      summary: Get an invoice
      description: Returns one invoice with line items and payments. Read-only.
      responses:
        '200': { $ref: '#/components/responses/InvoiceRecord' }
        <<: *common_member_errors
  /api/v1/email-campaigns:
    get:
      operationId: listEmailCampaigns
      x-required-scope: email_campaigns:read
      tags: [Marketing]
      summary: List email campaigns
      description: Returns campaign configuration and status without message body HTML. Read-only.
      parameters:
        - { $ref: '#/components/parameters/Page' }
        - { $ref: '#/components/parameters/PerPage' }
        - { name: status, in: query, schema: { $ref: '#/components/schemas/CampaignStatus' } }
        - { name: category, in: query, schema: { $ref: '#/components/schemas/CampaignCategory' } }
      responses:
        '200': { $ref: '#/components/responses/CampaignsCollection' }
        <<: *common_read_errors
  /api/v1/email-campaigns/{id}:
    parameters: [ { $ref: '#/components/parameters/Id' } ]
    get:
      operationId: getEmailCampaign
      x-required-scope: email_campaigns:read
      tags: [Marketing]
      summary: Get an email campaign
      description: Returns one campaign without message body HTML. Read-only.
      responses:
        '200': { $ref: '#/components/responses/CampaignRecord' }
        <<: *common_member_errors
  /api/v1/clients/{client_id}/unbilled-time-entries:
    parameters: [ { $ref: '#/components/parameters/ClientId' } ]
    get:
      operationId: listClientUnbilledTimeEntries
      x-required-scope: clients:unbilled_time_entries:read
      tags: [Finance]
      summary: List a client's unbilled time entries
      description: Returns all uninvoiced time entries for one tenant-scoped client. Read-only.
      responses:
        '200': { $ref: '#/components/responses/UnbilledTimeCollection' }
        <<: *common_member_errors
  /api/projects:
    get:
      operationId: listLegacyProjectOptions
      x-required-scope: projects:read
      deprecated: true
      tags: [Work]
      summary: Legacy project option list
      description: Compatibility operation returning compact project options. Use /api/v1/projects for new integrations.
      parameters: [ { name: client_id, in: query, required: false, description: Filter options to a client; omitted means projects without a client., schema: { type: integer, format: int64, minimum: 1 } } ]
      responses:
        '200': { $ref: '#/components/responses/ProjectOptions' }
        <<: *common_read_errors
  /api/project-tasks:
    get:
      operationId: listLegacyProjectTasks
      x-required-scope: tasks:read
      deprecated: true
      tags: [Work]
      summary: Legacy project task list
      description: Compatibility operation. Use /api/v1/project-tasks for new integrations.
      parameters:
        - { $ref: '#/components/parameters/Page' }
        - { $ref: '#/components/parameters/PerPage' }
        - { name: status, in: query, schema: { $ref: '#/components/schemas/TaskStatus' } }
        - { name: project_id, in: query, schema: { type: integer, format: int64, minimum: 1 } }
        - { name: assigned_user_id, in: query, schema: { type: integer, format: int64, minimum: 1 } }
        - { name: client_id, in: query, schema: { type: integer, format: int64, minimum: 1 } }
      responses:
        '200': { $ref: '#/components/responses/TasksCollection' }
        <<: *common_read_errors
  /api/tasks:
    get:
      operationId: listLegacyTimeEntryTaskOptions
      x-required-scope: tasks:read
      deprecated: true
      tags: [Work]
      summary: Legacy time-entry task option list
      description: Compatibility operation returning distinct task/client/project combinations.
      responses:
        '200': { $ref: '#/components/responses/TimeEntryTaskOptions' }
        <<: *common_read_errors
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Runvello API token
      description: Token issued from Admin > API access. Raw values begin with ldr_ and are shown once.
  parameters:
    Id: { name: id, in: path, required: true, description: Tenant-scoped record ID., schema: { type: integer, format: int64, minimum: 1 }, example: 12 }
    ClientId: { name: client_id, in: path, required: true, description: Tenant-scoped client ID., schema: { type: integer, format: int64, minimum: 1 }, example: 12 }
    Page: { name: page, in: query, description: One-based page number., schema: { type: integer, minimum: 1, default: 1 }, example: 1 }
    PerPage: { name: per_page, in: query, description: 'Records per page, capped at 100.', schema: { type: integer, minimum: 1, maximum: 100, default: 25 }, example: 25 }
    Search: { name: q, in: query, description: Case-insensitive name/email search., schema: { type: string, maxLength: 255 }, example: acme }
  responses:
    ClientsCollection: { $ref: '#/components/responses/GenericClientsCollection' }
    PeopleCollection: { $ref: '#/components/responses/GenericPeopleCollection' }
    LeadsCollection: { $ref: '#/components/responses/GenericLeadsCollection' }
    BookingsCollection: { $ref: '#/components/responses/GenericBookingsCollection' }
    ProjectsCollection: { $ref: '#/components/responses/GenericProjectsCollection' }
    TasksCollection: { $ref: '#/components/responses/GenericTasksCollection' }
    ContractsCollection: { $ref: '#/components/responses/GenericContractsCollection' }
    InvoicesCollection: { $ref: '#/components/responses/GenericInvoicesCollection' }
    CampaignsCollection: { $ref: '#/components/responses/GenericCampaignsCollection' }
    UnbilledTimeCollection: { $ref: '#/components/responses/GenericUnbilledTimeCollection' }
    GenericClientsCollection: { description: Paginated clients., content: { application/json: { schema: { $ref: '#/components/schemas/ClientsEnvelope' }, examples: { success: { value: { data: [ { id: 12, name: Acme Studio, client_type: company, status: active, email: hello@example.com } ], meta: { page: 1, per_page: 25, total_count: 1, total_pages: 1 } } } } } } }
    GenericPeopleCollection: { description: Paginated people., content: { application/json: { schema: { $ref: '#/components/schemas/PeopleEnvelope' }, examples: { success: { value: { data: [ { id: 21, client_id: 12, full_name: Taylor Jordan, email: taylor@example.com } ], meta: { page: 1, per_page: 25, total_count: 1, total_pages: 1 } } } } } } }
    GenericLeadsCollection: { description: Paginated leads., content: { application/json: { schema: { $ref: '#/components/schemas/LeadsEnvelope' }, examples: { success: { value: { data: [ { id: 31, full_name: Morgan Lee, email: morgan@example.com, stage: new, lead_intent: service_customer, conversion_target: tenant_client } ], meta: { page: 1, per_page: 25, total_count: 1, total_pages: 1 } } } } } } }
    GenericBookingsCollection: { description: Paginated bookings., content: { application/json: { schema: { $ref: '#/components/schemas/BookingsEnvelope' }, examples: { success: { value: { data: [ { id: 41, status: confirmed, starts_at: '2026-08-01T15:00:00Z', ends_at: '2026-08-01T15:30:00Z', booking_service_id: 4, user_id: 7 } ], meta: { page: 1, per_page: 25, total_count: 1, total_pages: 1 } } } } } } }
    GenericProjectsCollection: { description: Paginated projects., content: { application/json: { schema: { $ref: '#/components/schemas/ProjectsEnvelope' }, examples: { success: { value: { data: [ { id: 51, name: Brand launch, status: active, billing_type: hourly, client_id: 12 } ], meta: { page: 1, per_page: 25, total_count: 1, total_pages: 1 } } } } } } }
    GenericTasksCollection: { description: Paginated project tasks., content: { application/json: { schema: { $ref: '#/components/schemas/TasksEnvelope' }, examples: { success: { value: { data: [ { id: 61, name: Approve concepts, kind: task, status: pending, priority: medium, project_id: 51 } ], meta: { page: 1, per_page: 25, total_count: 1, total_pages: 1 } } } } } } }
    GenericContractsCollection: { description: Paginated contracts., content: { application/json: { schema: { $ref: '#/components/schemas/ContractsEnvelope' }, examples: { success: { value: { data: [ { id: 71, title: Services Agreement, status: sent, client_name: Acme Studio, price_cents: 25000, price_currency: USD } ], meta: { page: 1, per_page: 25, total_count: 1, total_pages: 1 } } } } } } }
    GenericInvoicesCollection: { description: Paginated invoices., content: { application/json: { schema: { $ref: '#/components/schemas/InvoicesEnvelope' }, examples: { success: { value: { data: [ { id: 81, invoice_number: INV-0001, status: sent, invoice_date: '2026-08-01', due_date: '2026-08-15', total: '250.00', amount_due: '250.00' } ], meta: { page: 1, per_page: 25, total_count: 1, total_pages: 1 } } } } } } }
    GenericCampaignsCollection: { description: Paginated campaigns., content: { application/json: { schema: { $ref: '#/components/schemas/CampaignsEnvelope' }, examples: { success: { value: { data: [ { id: 91, name: August newsletter, category: newsletter, delivery_mode: standalone, status: draft, step_count: 1 } ], meta: { page: 1, per_page: 25, total_count: 1, total_pages: 1 } } } } } } }
    GenericUnbilledTimeCollection: { description: Unbilled time entries for the client., content: { application/json: { schema: { $ref: '#/components/schemas/UnbilledTimeEnvelope' }, examples: { success: { value: { time_entries: [ { id: 101, task: Discovery, project_name: Brand launch, start_time: '08/01 09:00AM', end_time: '10:30AM', duration_hours: 1.5, rate: '150.00', earnings: '225.00' } ] } } } } } }
    ClientRecord: { description: Client record., content: { application/json: { schema: { $ref: '#/components/schemas/Client' }, examples: { success: { value: { id: 12, name: Acme Studio, client_type: company, status: active, email: hello@example.com, address: { city: Chicago, state: IL, country: USA }, created_at: '2026-07-12T12:00:00Z', updated_at: '2026-07-12T12:00:00Z' } } } } } }
    PersonRecord: { description: Person record., content: { application/json: { schema: { $ref: '#/components/schemas/Person' }, examples: { success: { value: { id: 21, client_id: 12, client_name: Acme Studio, full_name: Taylor Jordan, email: taylor@example.com, created_at: '2026-07-12T12:00:00Z', updated_at: '2026-07-12T12:00:00Z' } } } } } }
    LeadRecord: { description: Lead record., content: { application/json: { schema: { $ref: '#/components/schemas/Lead' }, examples: { success: { value: { id: 31, full_name: Morgan Lee, email: morgan@example.com, stage: qualified, lead_intent: service_customer, conversion_target: tenant_client, created_at: '2026-07-12T12:00:00Z', updated_at: '2026-07-12T12:00:00Z' } } } } } }
    BookingRecord: { description: Booking record., content: { application/json: { schema: { $ref: '#/components/schemas/Booking' }, examples: { success: { value: { id: 41, status: confirmed, payment_status: not_required, starts_at: '2026-08-01T15:00:00Z', ends_at: '2026-08-01T15:30:00Z', booking_service_id: 4, user_id: 7, created_at: '2026-07-12T12:00:00Z', updated_at: '2026-07-12T12:00:00Z' } } } } } }
    ContractRecord: { description: Contract record., content: { application/json: { schema: { $ref: '#/components/schemas/Contract' }, examples: { success: { value: { id: 71, title: Services Agreement, status: sent, service_type: Consulting, client_name: Acme Studio, client_email: hello@example.com, scope_of_work: Strategy, deliverables: Plan, timeline: Thirty days, usage_rights: Client use, payment_terms: Net 15, cancellation_terms: Seven days, price_cents: 25000, price_currency: USD, ai_generated: false, created_at: '2026-07-12T12:00:00Z', updated_at: '2026-07-12T12:00:00Z' } } } } } }
    InvoiceRecord: { description: Invoice record., content: { application/json: { schema: { $ref: '#/components/schemas/Invoice' }, examples: { success: { value: { id: 81, invoice_number: INV-0001, status: sent, invoice_date: '2026-08-01', due_date: '2026-08-15', total: '250.00', amount_paid: '0.00', amount_due: '250.00', line_items: [], payments: [], created_at: '2026-07-12T12:00:00Z', updated_at: '2026-07-12T12:00:00Z' } } } } } }
    CampaignRecord: { description: Email campaign record., content: { application/json: { schema: { $ref: '#/components/schemas/EmailCampaign' }, examples: { success: { value: { id: 91, name: August newsletter, category: newsletter, delivery_mode: standalone, status: draft, reply_to_email: owner@example.com, recurring_cadence: none, step_count: 1, created_at: '2026-07-12T12:00:00Z', updated_at: '2026-07-12T12:00:00Z' } } } } } }
    LeadConversionPreview: { description: Conversion preview., content: { application/json: { schema: { $ref: '#/components/schemas/LeadConversionPreview' }, examples: { client: { value: { lead_id: 31, conversion_target: tenant_client, invite_possible: true, warnings: [], client: { name: Morgan Lee, client_type: individual }, person: { full_name: Morgan Lee, email: morgan@example.com } } } } } } }
    LeadConversionResult: { description: Conversion result., content: { application/json: { schema: { $ref: '#/components/schemas/LeadConversionResult' }, examples: { client: { value: { conversion_target: tenant_client, client: { id: 12, name: Morgan Lee, client_type: individual }, person: { id: 21, full_name: Morgan Lee, email: morgan@example.com } } } } } } }
    ProjectOptions: { description: Compact project options., content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/ProjectOption' } }, examples: { success: { value: [ { id: 51, name: Brand launch } ] } } } } }
    TimeEntryTaskOptions: { description: Distinct time-entry task options., content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/TimeEntryTaskOption' } }, examples: { success: { value: [ { task: Discovery, client_id: 12, project_id: 51, source: project_task } ] } } } } }
    BadRequest: { description: Missing or malformed parameter., content: { application/json: { schema: { $ref: '#/components/schemas/Error' }, examples: { missing: { value: { error: 'Missing required parameter: client' } } } } } }
    Unauthorized: { description: 'Missing, invalid, expired, or revoked token.', content: { application/json: { schema: { $ref: '#/components/schemas/Error' }, examples: { invalid: { value: { error: Invalid or expired API token. } } } } } }
    Forbidden: { description: Plan or token scope does not allow the operation., content: { application/json: { schema: { $ref: '#/components/schemas/Error' }, examples: { scope: { value: { error: 'API token missing required scope: clients:write' } } } } } }
    NotFound: { description: Resource absent from this workspace., content: { application/json: { schema: { $ref: '#/components/schemas/Error' }, examples: { missing: { value: { error: Resource not found. } } } } } }
    ValidationFailed: { description: Validation or tenant context failure., content: { application/json: { schema: { $ref: '#/components/schemas/ValidationError' }, examples: { validation: { value: { error: Validation failed., errors: { full_name: [ "Full name can't be blank" ] } } } } } } }
    RateLimited: { description: Request rate exceeded., headers: { Retry-After: { description: Seconds before retry., schema: { type: integer, minimum: 1 } } }, content: { application/json: { schema: { $ref: '#/components/schemas/Error' }, examples: { limited: { value: { error: Rate limit exceeded. Please try again later. } } } } } }
    ServiceUnavailable: { description: Tenant schema temporarily unavailable., content: { application/json: { schema: { $ref: '#/components/schemas/Error' }, examples: { unavailable: { value: { error: Tenant schema is unavailable. } } } } } }
  schemas:
    ClientType: { type: string, enum: [individual, company] }
    ClientStatus: { type: string, enum: [active, onboarding, paused, archived] }
    LeadStage: { type: string, enum: [new, contacted, qualified, client, archived] }
    LeadIntent: { type: string, enum: [service_customer, platform_prospect, partner_prospect, support_request] }
    ConversionTarget: { type: string, enum: [tenant_client, organization_account] }
    BookingStatus: { type: string, enum: [payment_pending, requested, confirmed, canceled, completed] }
    BookingPaymentStatus: { type: string, enum: [not_required, pending, paid, failed, canceled, invoice_later] }
    ProjectStatus: { type: string, enum: [draft, active, on_hold, completed, archived, cancelled] }
    TaskStatus: { type: string, enum: [pending, in_progress, blocked, done] }
    ContractStatus: { type: string, enum: [draft, review, sent, signed] }
    InvoiceStatus: { type: string, enum: [draft, sent, viewed, paid, partially_paid, overdue, cancelled] }
    CampaignStatus: { type: string, enum: [draft, scheduled, sent] }
    CampaignCategory: { type: string, enum: [newsletter, lead_nurture, abandoned_intent, booking_lifecycle, customer_lifecycle, broadcast] }
    PaginationMeta:
      type: object
      additionalProperties: false
      required: [page, per_page, total_count, total_pages]
      properties:
        page: { type: integer, minimum: 1 }
        per_page: { type: integer, minimum: 1, maximum: 100 }
        total_count: { type: integer, minimum: 0 }
        total_pages: { type: integer, minimum: 0 }
    Address:
      type: object
      properties:
        line1: { type: [string, 'null'] }
        line2: { type: [string, 'null'] }
        city: { type: [string, 'null'] }
        state: { type: [string, 'null'] }
        zip_code: { type: [string, 'null'] }
        country: { type: [string, 'null'] }
    Client:
      type: object
      required: [id, name, client_type, status, address, created_at, updated_at]
      properties:
        id: { type: integer, format: int64 }
        name: { type: string }
        client_type: { $ref: '#/components/schemas/ClientType' }
        status: { $ref: '#/components/schemas/ClientStatus' }
        email: { type: [string, 'null'], format: email }
        phone: { type: [string, 'null'] }
        company_name: { type: [string, 'null'] }
        website: { type: [string, 'null'] }
        owner_user_id: { type: [integer, 'null'], format: int64 }
        client_slug: { type: string }
        client_subdomain: { type: [string, 'null'] }
        address: { $ref: '#/components/schemas/Address' }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
    Person:
      type: object
      required: [id, client_id, full_name, created_at, updated_at]
      properties:
        id: { type: integer, format: int64 }
        client_id: { type: integer, format: int64 }
        client_name: { type: string }
        full_name: { type: string }
        email: { type: [string, 'null'], format: email }
        phone_number: { type: [string, 'null'] }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
    Lead:
      type: object
      required: [id, full_name, stage, lead_intent, conversion_target, address, created_at, updated_at]
      properties:
        id: { type: integer, format: int64 }
        full_name: { type: string }
        company_name: { type: [string, 'null'] }
        email: { type: [string, 'null'], format: email }
        phone_number: { type: [string, 'null'] }
        stage: { $ref: '#/components/schemas/LeadStage' }
        lead_intent: { $ref: '#/components/schemas/LeadIntent' }
        conversion_target: { $ref: '#/components/schemas/ConversionTarget' }
        owner_user_id: { type: [integer, 'null'], format: int64 }
        website: { type: [string, 'null'] }
        lead_source_url: { type: [string, 'null'] }
        capture_source_kind: { type: [string, 'null'] }
        capture_source_path: { type: [string, 'null'] }
        current_competitor: { type: [string, 'null'] }
        team_size: { type: [string, 'null'] }
        address: { type: object, properties: { office_address: { type: [string, 'null'] }, city: { type: [string, 'null'] }, state: { type: [string, 'null'] }, zip_code: { type: [string, 'null'] } } }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
    Booking:
      type: object
      required: [id, status, payment_status, starts_at, ends_at, booking_service_id, user_id, created_at, updated_at]
      properties:
        id: { type: integer, format: int64 }
        status: { $ref: '#/components/schemas/BookingStatus' }
        payment_status: { $ref: '#/components/schemas/BookingPaymentStatus' }
        starts_at: { type: string, format: date-time }
        ends_at: { type: string, format: date-time }
        time_zone: { type: [string, 'null'] }
        location: { type: [string, 'null'] }
        guest_name: { type: [string, 'null'] }
        guest_email: { type: [string, 'null'], format: email }
        guest_phone: { type: [string, 'null'] }
        booking_service_id: { type: integer, format: int64 }
        booking_service_name: { type: [string, 'null'] }
        client_id: { type: [integer, 'null'], format: int64 }
        client_name: { type: [string, 'null'] }
        person_id: { type: [integer, 'null'], format: int64 }
        person_name: { type: [string, 'null'] }
        project_id: { type: [integer, 'null'], format: int64 }
        project_name: { type: [string, 'null'] }
        user_id: { type: integer, format: int64 }
        user_email: { type: string, format: email }
        canceled_at: { type: [string, 'null'], format: date-time }
        rescheduled_at: { type: [string, 'null'], format: date-time }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
    Project:
      type: object
      required: [id, name, status, billing_type, created_at, updated_at]
      properties:
        id: { type: integer, format: int64 }
        name: { type: string }
        status: { $ref: '#/components/schemas/ProjectStatus' }
        billing_type: { type: string, enum: [hourly, fixed_price] }
        rate: { type: [string, 'null'] }
        fixed_project_cost: { type: [string, 'null'] }
        starts_on: { type: [string, 'null'], format: date }
        target_due_on: { type: [string, 'null'], format: date }
        completed_on: { type: [string, 'null'], format: date }
        client_id: { type: [integer, 'null'], format: int64 }
        client_name: { type: [string, 'null'] }
        owner_user_id: { type: [integer, 'null'], format: int64 }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
    ProjectTask:
      type: object
      required: [id, name, kind, status, priority, visible_to_client, created_at, updated_at]
      properties:
        id: { type: integer, format: int64 }
        name: { type: string }
        kind: { type: string, enum: [task, milestone] }
        status: { $ref: '#/components/schemas/TaskStatus' }
        priority: { type: string, enum: [low, medium, high, urgent] }
        due_on: { type: [string, 'null'], format: date }
        completed_at: { type: [string, 'null'], format: date-time }
        visible_to_client: { type: boolean }
        project_id: { type: [integer, 'null'], format: int64 }
        client_id: { type: [integer, 'null'], format: int64 }
        assigned_user_id: { type: [integer, 'null'], format: int64 }
        depends_on_task_id: { type: [integer, 'null'], format: int64 }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        project: { type: [object, 'null'], properties: { id: { type: integer }, name: { type: string }, client_id: { type: [integer, 'null'] }, client_name: { type: [string, 'null'] } } }
    Contract:
      type: object
      required: [id, title, status, service_type, client_name, client_email, scope_of_work, deliverables, timeline, usage_rights, payment_terms, cancellation_terms, price_cents, price_currency, ai_generated, created_at, updated_at]
      properties:
        id: { type: integer, format: int64 }
        title: { type: string }
        status: { $ref: '#/components/schemas/ContractStatus' }
        service_type: { type: string }
        client_id: { type: [integer, 'null'], format: int64 }
        client_name: { type: string }
        client_email: { type: string, format: email }
        project_id: { type: [integer, 'null'], format: int64 }
        scope_of_work: { type: string }
        deliverables: { type: string }
        timeline: { type: string }
        usage_rights: { type: string }
        payment_terms: { type: string }
        cancellation_terms: { type: string }
        price_cents: { type: integer, minimum: 0 }
        price_currency: { type: string, minLength: 3, maxLength: 3 }
        jurisdiction_code: { type: [string, 'null'] }
        ai_generated: { type: boolean }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
    Invoice:
      type: object
      required: [id, invoice_number, status, invoice_date, due_date, line_items, payments, created_at, updated_at]
      properties:
        id: { type: integer, format: int64 }
        invoice_number: { type: string }
        status: { $ref: '#/components/schemas/InvoiceStatus' }
        invoice_date: { type: string, format: date }
        due_date: { type: string, format: date }
        paid_date: { type: [string, 'null'], format: date }
        sent_at: { type: [string, 'null'], format: date-time }
        sent_to_email: { type: [string, 'null'], format: email }
        client_id: { type: integer, format: int64 }
        client_name: { type: string }
        client_email: { type: [string, 'null'], format: email }
        subtotal: { type: [string, 'null'] }
        discount_amount: { type: [string, 'null'] }
        tax_amount: { type: [string, 'null'] }
        total: { type: [string, 'null'] }
        amount_paid: { type: [string, 'null'] }
        amount_due: { type: [string, 'null'] }
        stripe_invoice_id: { type: [string, 'null'] }
        stripe_invoice_status: { type: [string, 'null'] }
        stripe_invoice_url: { type: [string, 'null'], format: uri }
        stripe_invoice_pdf_url: { type: [string, 'null'], format: uri }
        stripe_sent_at: { type: [string, 'null'], format: date-time }
        line_items: { type: array, items: { $ref: '#/components/schemas/InvoiceLineItem' } }
        payments: { type: array, items: { $ref: '#/components/schemas/InvoicePayment' } }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
    InvoiceLineItem: { type: object, properties: { id: { type: integer }, description: { type: string }, quantity: { type: string }, rate: { type: string }, amount: { type: string }, project_id: { type: [integer, 'null'] }, time_entry_id: { type: [integer, 'null'] } } }
    InvoicePayment: { type: object, properties: { id: { type: integer }, amount: { type: string }, payment_date: { type: string, format: date }, payment_method: { type: string }, reference_number: { type: [string, 'null'] } } }
    EmailCampaign:
      type: object
      required: [id, name, category, delivery_mode, status, reply_to_email, step_count, created_at, updated_at]
      properties:
        id: { type: integer, format: int64 }
        name: { type: string }
        description: { type: [string, 'null'] }
        goal: { type: [string, 'null'] }
        category: { $ref: '#/components/schemas/CampaignCategory' }
        delivery_mode: { type: string, enum: [standalone, automated] }
        status: { $ref: '#/components/schemas/CampaignStatus' }
        reply_to_email: { type: string, format: email }
        email_segment_id: { type: [integer, 'null'] }
        email_template_id: { type: [integer, 'null'] }
        offer_id: { type: [integer, 'null'] }
        scheduled_at: { type: [string, 'null'], format: date-time }
        sent_at: { type: [string, 'null'], format: date-time }
        recurring_cadence: { type: string, enum: [none, weekly, monthly] }
        step_count: { type: integer, minimum: 0 }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
    UnbilledTimeEntry:
      type: object
      properties:
        id: { type: integer, format: int64 }
        task: { type: string }
        project_name: { type: [string, 'null'] }
        start_time: { type: [string, 'null'], description: Workspace-local formatted start time. }
        end_time: { type: [string, 'null'], description: Workspace-local formatted end time. }
        duration_hours: { type: number, format: double }
        rate: { type: [string, 'null'], pattern: '^-?\d+\.\d{2}$', description: Fixed-decimal monetary value. }
        earnings: { type: [string, 'null'], pattern: '^-?\d+\.\d{2}$', description: Fixed-decimal monetary value. }
    ClientWriteEnvelope:
      type: object
      additionalProperties: false
      required: [client]
      properties:
        client:
          type: object
          additionalProperties: false
          properties:
            name: { type: string }
            client_type: { $ref: '#/components/schemas/ClientType' }
            status: { $ref: '#/components/schemas/ClientStatus' }
            company_name: { type: string }
            industry: { type: string }
            website: { type: string }
            email: { type: string, format: email }
            first_name: { type: string }
            last_name: { type: string }
            phone: { type: string }
            address_line1: { type: string }
            address_line2: { type: string }
            city: { type: string }
            state: { type: string }
            zip_code: { type: string }
            country: { type: string }
            owner_user_id: { type: integer, format: int64 }
    PersonWriteEnvelope:
      type: object
      additionalProperties: false
      required: [person]
      properties:
        person: { type: object, additionalProperties: false, properties: { full_name: { type: string }, email: { type: string, format: email }, phone_number: { type: string } } }
    LeadWriteEnvelope:
      type: object
      additionalProperties: false
      required: [lead]
      properties:
        lead:
          type: object
          additionalProperties: false
          properties:
            full_name: { type: string }
            company_name: { type: string }
            email: { type: string, format: email }
            phone_number: { type: string }
            stage: { $ref: '#/components/schemas/LeadStage' }
            lead_intent: { $ref: '#/components/schemas/LeadIntent' }
            conversion_target: { $ref: '#/components/schemas/ConversionTarget' }
            owner_user_id: { type: integer, format: int64 }
            website: { type: string }
            lead_source_url: { type: string }
            office_address: { type: string }
            city: { type: string }
            state: { type: string }
            zip_code: { type: string }
    BookingWriteEnvelope:
      type: object
      additionalProperties: false
      required: [booking]
      properties:
        booking:
          type: object
          additionalProperties: false
          properties:
            booking_service_id: { type: integer, format: int64 }
            client_id: { type: integer, format: int64 }
            person_id: { type: integer, format: int64 }
            project_id: { type: integer, format: int64 }
            user_id: { type: integer, format: int64 }
            status: { $ref: '#/components/schemas/BookingStatus' }
            starts_at: { type: string, format: date-time }
            time_zone: { type: string }
            location: { type: string }
            notes: { type: string }
            guest_name: { type: string }
            guest_email: { type: string, format: email }
            guest_phone: { type: string }
    LeadConversionEnvelope:
      type: object
      additionalProperties: false
      required: [conversion]
      properties:
        conversion:
          type: object
          additionalProperties: false
          properties:
            conversion_target: { $ref: '#/components/schemas/ConversionTarget' }
            client_name: { type: string }
            client_type: { $ref: '#/components/schemas/ClientType' }
            invite_user: { type: boolean }
            organization_name: { type: string }
            subdomain: { type: string, pattern: '^[a-z0-9][a-z0-9-]*$' }
    LeadConversionPreview:
      type: object
      required: [lead_id, conversion_target, invite_possible, warnings]
      properties:
        lead_id: { type: integer }
        conversion_target: { $ref: '#/components/schemas/ConversionTarget' }
        invite_possible: { type: boolean }
        warnings: { type: array, items: { type: string } }
        client: { type: object, additionalProperties: true }
        person: { type: object, additionalProperties: true }
        organization: { type: object, additionalProperties: true }
        invite: { type: object, additionalProperties: true }
    LeadConversionResult:
      oneOf:
        - type: object
          required: [conversion_target, client, person]
          properties: { conversion_target: { const: tenant_client }, client: { type: object }, person: { type: object } }
        - type: object
          required: [conversion_target, organization, invite]
          properties: { conversion_target: { const: organization_account }, organization: { type: object }, invite: { type: object } }
    ProjectOption: { type: object, required: [id, name], properties: { id: { type: integer }, name: { type: string } } }
    TimeEntryTaskOption: { type: object, additionalProperties: false, required: [task, client_id, project_id, source], properties: { task: { type: string }, client_id: { type: integer }, project_id: { type: integer }, source: { type: string, enum: [project_task, time_entry] } } }
    ClientsEnvelope: { $ref: '#/components/schemas/ClientsPaginated' }
    ClientsPaginated: { type: object, required: [data, meta], properties: { data: { type: array, items: { $ref: '#/components/schemas/Client' } }, meta: { $ref: '#/components/schemas/PaginationMeta' } } }
    PeopleEnvelope: { type: object, required: [data, meta], properties: { data: { type: array, items: { $ref: '#/components/schemas/Person' } }, meta: { $ref: '#/components/schemas/PaginationMeta' } } }
    LeadsEnvelope: { type: object, required: [data, meta], properties: { data: { type: array, items: { $ref: '#/components/schemas/Lead' } }, meta: { $ref: '#/components/schemas/PaginationMeta' } } }
    BookingsEnvelope: { type: object, required: [data, meta], properties: { data: { type: array, items: { $ref: '#/components/schemas/Booking' } }, meta: { $ref: '#/components/schemas/PaginationMeta' } } }
    ProjectsEnvelope: { type: object, required: [data, meta], properties: { data: { type: array, items: { $ref: '#/components/schemas/Project' } }, meta: { $ref: '#/components/schemas/PaginationMeta' } } }
    TasksEnvelope: { type: object, required: [data, meta], properties: { data: { type: array, items: { $ref: '#/components/schemas/ProjectTask' } }, meta: { $ref: '#/components/schemas/PaginationMeta' } } }
    ContractsEnvelope: { type: object, required: [data, meta], properties: { data: { type: array, items: { $ref: '#/components/schemas/Contract' } }, meta: { $ref: '#/components/schemas/PaginationMeta' } } }
    InvoicesEnvelope: { type: object, required: [data, meta], properties: { data: { type: array, items: { $ref: '#/components/schemas/Invoice' } }, meta: { $ref: '#/components/schemas/PaginationMeta' } } }
    CampaignsEnvelope: { type: object, required: [data, meta], properties: { data: { type: array, items: { $ref: '#/components/schemas/EmailCampaign' } }, meta: { $ref: '#/components/schemas/PaginationMeta' } } }
    UnbilledTimeEnvelope: { type: object, additionalProperties: false, required: [time_entries], properties: { time_entries: { type: array, items: { $ref: '#/components/schemas/UnbilledTimeEntry' } } } }
    Error:
      type: object
      additionalProperties: false
      required: [error]
      properties: { error: { type: string } }
    ValidationError:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error: { type: string }
        errors: { type: object, additionalProperties: { type: array, items: { type: string } } }
