openapi: 3.1.0
info:
  title: Evenhand REST API
  version: 1.0.0
  summary: Brokerage-facing REST API for the Evenhand deal-management platform.
  description: |
    The Evenhand REST API exposes brokerage-tenant resources (contacts, deals,
    offers, diligence artifacts) over HTTPS using bearer-token authentication
    issued from the brokerage settings UI.

    All requests are scoped to a single brokerage. The bearer token determines
    both the brokerage tenant and the set of permitted scopes; nothing about
    the URL or request body changes the tenant.
  contact:
    name: Evenhand
    url: https://developers.evenhandhq.com
  license:
    name: Proprietary
    identifier: LicenseRef-Evenhand-Proprietary
servers:
  - url: https://app.evenhandhq.com/api/v1
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Contacts
    description: People and companies tracked by a brokerage outside of any specific deal.
paths:
  /contacts:
    get:
      tags:
        - Contacts
      summary: List contacts
      description: Returns contacts visible to the calling brokerage. Paginated.
      operationId: listContacts
      parameters:
        - name: cursor
          in: query
          description: Opaque cursor returned by a previous response.
          schema:
            type: string
        - name: limit
          in: query
          description: Page size. Defaults to 50, max 200.
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
      responses:
        '200':
          description: A page of contacts.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/RateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
    post:
      tags:
        - Contacts
      summary: Create a contact
      operationId: createContact
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactCreate'
      responses:
        '201':
          description: Contact created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /contacts/{contactId}:
    parameters:
      - name: contactId
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      tags:
        - Contacts
      summary: Retrieve a contact
      operationId: getContact
      responses:
        '200':
          description: A single contact.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: evhk_<env>_<32hex>
      description: |
        API keys are issued from the brokerage settings UI by an admin. The raw
        token is shown exactly once at creation; only its SHA-256 digest is
        retained server-side. Tokens carry a fixed scope set and a per-key
        rate-limit ceiling.
  headers:
    RateLimitLimit:
      description: Per-key request quota for the current window.
      schema:
        type: integer
    RateLimitRemaining:
      description: Requests remaining in the current window.
      schema:
        type: integer
    RateLimitReset:
      description: Unix epoch seconds when the current window resets.
      schema:
        type: integer
    RetryAfter:
      description: Seconds to wait before retrying.
      schema:
        type: integer
  schemas:
    Contact:
      type: object
      required:
        - id
        - type
        - displayName
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
          enum:
            - person
            - company
        displayName:
          type: string
        email:
          type:
            - string
            - 'null'
          format: email
        phone:
          type:
            - string
            - 'null'
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    ContactCreate:
      type: object
      required:
        - type
        - displayName
      properties:
        type:
          type: string
          enum:
            - person
            - company
        displayName:
          type: string
          minLength: 1
        email:
          type: string
          format: email
        phone:
          type: string
    ContactList:
      type: object
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Contact'
        pagination:
          $ref: '#/components/schemas/Pagination'
    Pagination:
      type: object
      required:
        - hasMore
      properties:
        hasMore:
          type: boolean
        nextCursor:
          type:
            - string
            - 'null'
    ErrorEnvelope:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: Canonical machine-readable error code.
              enum:
                - invalid_token
                - insufficient_scope
                - rate_limited
                - validation_error
                - contact_not_found
            message:
              type: string
              description: Human-readable summary of the error.
            details:
              description: Optional structured context for the error.
              type: object
              additionalProperties: true
  responses:
    Unauthorized:
      description: Missing or invalid bearer token.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    Forbidden:
      description: Token is valid but lacks the required scope.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    NotFound:
      description: The requested resource does not exist or is not visible to this token.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    ValidationError:
      description: Request body failed validation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    RateLimited:
      description: Per-key or per-IP rate limit exceeded.
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
