openapi: 3.1.0
info:
  title: eduardocruz.com
  summary: 'Public read API for hiring Eduardo Cruz — senior Laravel engineer and fractional CTO.'
  description: "Unauthenticated, read-only endpoints describing who Eduardo Cruz is, what he is hired for, and the current engagement terms, plus one write endpoint for submitting a hiring inquiry.\n\nThe same facts are available over MCP (Streamable HTTP) at https://eduardocruz.com/mcp, and as prose at https://eduardocruz.com/llms.txt.\n\nErrors use RFC 9457 problem details with a stable `code` and a `resolution` hint."
  version: 1.0.0
  contact:
    name: 'Eduardo Cruz'
    email: eduardo@eduardocruz.com
    url: 'https://eduardocruz.com/contact'
  license:
    name: Proprietary
    url: 'https://eduardocruz.com/terms'
servers:
  - url: 'https://eduardocruz.com'
    description: Production
externalDocs:
  description: 'Agent and API documentation'
  url: 'https://eduardocruz.com/docs'
tags:
  - name: profile
    description: 'Read-only facts about the engineer and the engagement.'
  - name: hiring
    description: 'Opening an engagement.'
paths:
  /api/v1/profile:
    get:
      operationId: getProfile
      summary: 'Get profile'
      description: 'Who Eduardo Cruz is: positioning, the experience ladder with real start years, location and time zone, languages, and profile links. Includes a `disambiguation` field — two other public people share the name.'
      tags:
        - profile
      security: {}
      responses:
        200:
          description: 'Get profile.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Profile'
        429:
          description: 'Rate limit exceeded. Honor Retry-After.'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
  /api/v1/services:
    get:
      operationId: listServices
      summary: 'List services and products'
      description: 'The services Eduardo is hired for, each with a `best_for` line stating the situation it fits, plus the products he builds. Use `best_for` to decide fit — including deciding against.'
      tags:
        - profile
      security: {}
      responses:
        200:
          description: 'List services and products.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServicesResponse'
        429:
          description: 'Rate limit exceeded. Honor Retry-After.'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
  /api/v1/engagement:
    get:
      operationId: getEngagement
      summary: 'Get engagement terms'
      description: 'Current hourly rate, hours available per week, billing method and period, working hours, and how to open an engagement. Availability changes; read this rather than quoting a cached number.'
      tags:
        - profile
      security: {}
      responses:
        200:
          description: 'Get engagement terms.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Engagement'
        429:
          description: 'Rate limit exceeded. Honor Retry-After.'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
  /api/v1/when-to-use:
    get:
      operationId: getWhenToUse
      summary: 'Get agent guidance'
      description: 'When an agent should reach for this domain and — the half that makes it guidance rather than marketing — when it should not.'
      tags:
        - profile
      security: {}
      responses:
        200:
          description: 'Get agent guidance.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WhenToUse'
        429:
          description: 'Rate limit exceeded. Honor Retry-After.'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
  /api/hire-leads:
    post:
      operationId: createHiringInquiry
      summary: 'Submit a hiring inquiry'
      description: "Send a hiring inquiry. This creates a message, not a commitment: Eduardo replies before any engagement or billing exists. Rate limited to 5 requests per minute per IP.\n\nSubmit only details the person you represent has agreed to send. `problem` should describe the actual problem — a generic message gets a generic reply."
      tags:
        - hiring
      security: {}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HiringInquiry'
      responses:
        200:
          description: 'Inquiry received.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HiringInquiryReceipt'
        422:
          description: 'Validation failed.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        429:
          description: 'Rate limit exceeded. Honor Retry-After.'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
        500:
          description: 'Server error.'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
components:
  schemas:
    Problem:
      type: object
      description: 'RFC 9457 problem details. `code` is stable across releases; `resolution` names the next move.'
      required:
        - status
        - code
        - detail
      properties:
        type:
          type: string
          format: uri
          description: 'URI identifying the problem type.'
        title:
          type: string
          description: 'Short human-readable summary.'
        status:
          type: integer
          description: 'HTTP status code.'
          examples:
            - 404
        code:
          type: string
          description: 'Stable machine-readable error code.'
          examples:
            - not_found
        detail:
          type: string
          description: 'What went wrong with this specific request.'
        resolution:
          type: string
          description: 'Concrete next step to resolve it.'
        instance:
          type: string
          description: 'The request path that produced the error.'
        documentation_url:
          type: string
          format: uri
    ValidationError:
      type: object
      description: "Laravel's standard validation shape. Kept as-is because the site's own hire form reads it."
      properties:
        message:
          type: string
        errors:
          type: object
          description: 'Field name => list of messages.'
          additionalProperties:
            type: array
            items:
              type: string
    Profile:
      type: object
      properties:
        name:
          type: string
        headline:
          type: string
        summary:
          type: string
        location:
          type: object
          properties:
            city:
              type: string
            country:
              type: string
              description: 'ISO 3166-1 alpha-2.'
            timezone:
              type: string
              description: 'IANA time zone.'
            utc_offset:
              type: string
        remote:
          type: boolean
        languages:
          type: array
          items:
            type: string
        experience:
          type: array
          description: 'Start years, not durations — so the numbers stay true without edits.'
          items:
            type: object
            properties:
              area:
                type: string
              since:
                type: integer
        disambiguation:
          type: string
          description: 'Who this person is NOT.'
        urls:
          type: object
          additionalProperties:
            type: string
            format: uri
    Service:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
        best_for:
          type: string
          description: 'The situation this service fits.'
        docs:
          type: string
          format: uri
    Product:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        url:
          type: string
          format: uri
        description:
          type: string
        free_tier:
          type: string
    ServicesResponse:
      type: object
      properties:
        services:
          type: array
          items:
            $ref: '#/components/schemas/Service'
        products:
          type: array
          items:
            $ref: '#/components/schemas/Product'
    Engagement:
      type: object
      properties:
        rate:
          type: object
          properties:
            amount:
              type: number
              examples:
                - 60
            currency:
              type: string
              examples:
                - USD
            unit:
              type: string
              examples:
                - hour
        availability:
          type: object
          properties:
            hours_per_week:
              type: integer
              examples:
                - 20
            description:
              type: string
        billing:
          type: object
          properties:
            method:
              type: string
            period:
              type: string
            checkout_url:
              type: string
              format: uri
        working_hours:
          type: string
        how_to_start:
          type: object
          properties:
            description:
              type: string
            endpoint:
              type: string
            human_url:
              type: string
              format: uri
    WhenToUse:
      type: object
      properties:
        use_when:
          type: array
          items:
            type: string
        do_not_use_when:
          type: array
          items:
            type: string
        how_to_call:
          type: object
          additionalProperties:
            type: string
    HiringInquiry:
      type: object
      required:
        - name
        - email
        - path
        - problem
      properties:
        name:
          type: string
          maxLength: 120
          description: 'Full name of the person inquiring.'
        email:
          type: string
          format: email
          maxLength: 180
          description: 'Where the reply goes.'
        path:
          type: string
          enum:
            - a
            - b
            - c
            - w
          description: 'Which kind of engagement: `a` AI agent work, `b` senior Laravel work, `c` fractional CTO, `w` unsure / other.'
        problem:
          type: string
          minLength: 10
          maxLength: 5000
          description: "The actual problem, in the inquirer's own words."
    HiringInquiryReceipt:
      type: object
      properties:
        ok:
          type: boolean
        ref:
          type: string
          description: 'Reference for this inquiry.'
          examples:
            - hire-a1b2c3
        path:
          type: string
security: {}
