{
  "openapi": "3.0.3",
  "info": {
    "title": "RemoteHands Public Gig Actions",
    "version": "1.0.0",
    "description": "No-auth public actions for discovering published RemoteHands gigs in Bangkok and submitting interest leads. The agent-to-RemoteHands protocol is English-only. Lead submission creates an interest lead only, not an account, application, or confirmed booking."
  },
  "servers": [
    {
      "url": "https://remotehands.live",
      "description": "RemoteHands production"
    }
  ],
  "paths": {
    "/api/public/gigs/": {
      "get": {
        "operationId": "searchPublicGigs",
        "summary": "Search public gigs",
        "description": "Search venue-published public RemoteHands gigs. Defaults to Bangkok when city is omitted. Returns only visible, published, requestable gigs with remaining spots. All request parameters and response fields use the English public contract.",
        "security": [],
        "x-openai-isConsequential": false,
        "parameters": [
          {
            "name": "city",
            "in": "query",
            "required": false,
            "description": "City to search. Defaults to Bangkok when omitted or blank.",
            "schema": {
              "type": "string",
              "default": "Bangkok",
              "example": "Bangkok"
            }
          },
          {
            "name": "worker_type",
            "in": "query",
            "required": false,
            "description": "Optional gig category filter.",
            "schema": {
              "type": "string",
              "enum": ["staffing", "entertainer"],
              "example": "staffing"
            }
          },
          {
            "name": "date_from",
            "in": "query",
            "required": false,
            "description": "Optional inclusive start date filter in YYYY-MM-DD format.",
            "schema": {
              "type": "string",
              "format": "date",
              "example": "2026-07-01"
            }
          },
          {
            "name": "date_to",
            "in": "query",
            "required": false,
            "description": "Optional inclusive end date filter in YYYY-MM-DD format. Must be on or after date_from when both are provided.",
            "schema": {
              "type": "string",
              "format": "date",
              "example": "2026-07-07"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of gigs to return.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20,
              "example": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Public gig search results.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicGigSearchResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/api/public/gigs/{slug}/": {
      "get": {
        "operationId": "getPublicGig",
        "summary": "Read one public gig",
        "description": "Read sanitized public details for one published gig by slug, plus lead and signup state. The payload does not include private worksite street addresses, private venue setup notes, lead contact data, applicant lists, assignments, identity documents, operational logs, or admin state.",
        "security": [],
        "x-openai-isConsequential": false,
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Public gig slug from searchPublicGigs.",
            "schema": {
              "type": "string",
              "example": "harbor-house-floor-team-20260704"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sanitized public gig detail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicGig"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/gigs/{slug}/leads/": {
      "post": {
        "operationId": "createPublicGigLead",
        "summary": "Create a public gig interest lead",
        "description": "Create an interest lead for a public gig after the assistant has collected required contact details and explicit consent. This is a consequential action. It does not create an account, application, or confirmed booking.",
        "security": [],
        "x-openai-isConsequential": true,
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Public gig slug from searchPublicGigs or getPublicGig.",
            "schema": {
              "type": "string",
              "example": "harbor-house-floor-team-20260704"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Interest lead details. At least one of phone, line_id, or email is required, and consent_to_contact must be true.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicGigLeadRequest"
              },
              "example": {
                "name": "Nok Prospect",
                "phone": "0812345678",
                "line_id": "nok-line",
                "experience_note": "Banquet service and floor team shifts.",
                "availability_note": "Available for the full shift.",
                "source": "chatgpt",
                "consent_to_contact": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Interest lead created. The returned signup_url lets the person continue account creation in RemoteHands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicGigLeadReceipt"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/LeadClosed"
          }
        }
      }
    }
  },
  "components": {
    "responses": {
      "ValidationError": {
        "description": "Validation error. Error messages are English-only.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "phone": ["Add at least one contact method: phone, LINE, or email."],
              "line_id": ["Add at least one contact method: phone, LINE, or email."],
              "email": ["Add at least one contact method: phone, LINE, or email."]
            }
          }
        }
      },
      "NotFound": {
        "description": "The public gig was not found or is not visible.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "detail": "Not found."
            }
          }
        }
      },
      "LeadClosed": {
        "description": "The public gig is no longer accepting interest leads.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "detail": "This gig is no longer accepting interest forms."
            }
          }
        }
      }
    },
    "schemas": {
      "PublicGigSearchResponse": {
        "type": "object",
        "required": ["city", "count", "results"],
        "properties": {
          "city": {
            "type": "string",
            "description": "City used for the search.",
            "example": "Bangkok"
          },
          "count": {
            "type": "integer",
            "minimum": 0,
            "description": "Number of public gigs returned.",
            "example": 2
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicGig"
            }
          }
        }
      },
      "PublicGig": {
        "type": "object",
        "required": [
          "slug",
          "shift_id",
          "title",
          "starts_at",
          "ends_at",
          "worker_type",
          "worker_type_label",
          "hourly_rate",
          "payment_method",
          "slots",
          "venue",
          "location",
          "published_at",
          "accepts_leads",
          "signup_path"
        ],
        "properties": {
          "slug": {
            "type": "string",
            "description": "Public gig slug.",
            "example": "harbor-house-floor-team-20260704"
          },
          "shift_id": {
            "type": "integer",
            "description": "Internal shift id used only to preserve signup context.",
            "example": 42
          },
          "title": {
            "type": "string",
            "description": "Public gig title.",
            "example": "Harbor House floor team"
          },
          "event_group_id": {
            "type": "string",
            "nullable": true,
            "description": "Optional public event grouping id.",
            "example": "8a0f37d2-1827-46d4-97f7-d01c70f4e2c1"
          },
          "event_title": {
            "type": "string",
            "description": "Optional public event title.",
            "example": "Harbor House private dinner"
          },
          "description": {
            "type": "string",
            "description": "Public gig description.",
            "example": "Support dinner service and floor reset."
          },
          "starts_at": {
            "type": "string",
            "format": "date-time",
            "description": "Gig start time in ISO 8601 format.",
            "example": "2026-07-04T18:00:00+07:00"
          },
          "ends_at": {
            "type": "string",
            "format": "date-time",
            "description": "Gig end time in ISO 8601 format.",
            "example": "2026-07-04T23:00:00+07:00"
          },
          "worker_type": {
            "type": "string",
            "description": "Public gig category.",
            "enum": ["staffing", "entertainer"],
            "example": "staffing"
          },
          "worker_type_label": {
            "type": "string",
            "description": "English display label for worker_type.",
            "example": "Staffing"
          },
          "hourly_rate": {
            "type": "string",
            "description": "Hourly rate amount in Thai baht as a decimal string.",
            "example": "600.00"
          },
          "event_budget_amount": {
            "type": "string",
            "nullable": true,
            "description": "Optional event budget amount as a decimal string.",
            "example": "2500.00"
          },
          "event_budget_basis": {
            "type": "string",
            "description": "Optional event budget basis. Blank string means no public event budget.",
            "enum": ["per_person", "total", ""],
            "example": "per_person"
          },
          "payment_method": {
            "type": "string",
            "description": "Public payment method enum.",
            "enum": ["pay_at_venue"],
            "example": "pay_at_venue"
          },
          "payment_method_label": {
            "type": "string",
            "description": "English display label for payment_method.",
            "example": "Pay at venue"
          },
          "slots": {
            "type": "integer",
            "minimum": 1,
            "description": "Total public slots for the gig.",
            "example": 2
          },
          "remaining_spots": {
            "type": "integer",
            "minimum": 0,
            "description": "Remaining public spots available for interest leads.",
            "example": 2
          },
          "venue": {
            "$ref": "#/components/schemas/PublicGigVenue"
          },
          "location": {
            "$ref": "#/components/schemas/PublicGigLocation"
          },
          "published_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Time when the public gig page was published.",
            "example": "2026-06-29T09:00:00+07:00"
          },
          "accepts_leads": {
            "type": "boolean",
            "description": "Whether the gig currently accepts public interest leads.",
            "example": true
          },
          "public_path": {
            "type": "string",
            "description": "Relative path to the public gig page.",
            "example": "/events/harbor-house-floor-team-20260704"
          },
          "public_url": {
            "type": "string",
            "format": "uri",
            "description": "Absolute URL to the public gig page.",
            "example": "https://remotehands.live/events/harbor-house-floor-team-20260704"
          },
          "signup_path": {
            "type": "string",
            "description": "Relative RemoteHands signup path preserving gig context.",
            "example": "/signup?signup_type=worker&worker_type=staffing&public_shift=harbor-house-floor-team-20260704&shift=42"
          },
          "signup_url": {
            "type": "string",
            "format": "uri",
            "description": "Absolute RemoteHands signup URL preserving gig context.",
            "example": "https://remotehands.live/signup?signup_type=worker&worker_type=staffing&public_shift=harbor-house-floor-team-20260704&shift=42"
          }
        }
      },
      "PublicGigVenue": {
        "type": "object",
        "required": ["id", "account_type", "venue_name", "city"],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Public venue id.",
            "example": 7
          },
          "account_type": {
            "type": "string",
            "description": "Venue account type.",
            "enum": ["business", "independent_planner"],
            "example": "business"
          },
          "venue_name": {
            "type": "string",
            "description": "Public venue name.",
            "example": "Harbor House"
          },
          "city": {
            "type": "string",
            "description": "Venue city.",
            "example": "Bangkok"
          },
          "short_blurb": {
            "type": "string",
            "description": "Public venue summary.",
            "example": "Private dining and event team"
          },
          "image_url": {
            "type": "string",
            "nullable": true,
            "description": "Optional public venue image URL.",
            "example": "https://remotehands.live/media/venues/harbor-house.jpg"
          },
          "logo_url": {
            "type": "string",
            "nullable": true,
            "description": "Optional public venue logo URL.",
            "example": "https://remotehands.live/media/venues/harbor-house-logo.jpg"
          },
          "operation_type": {
            "type": "string",
            "description": "Public venue operation type.",
            "example": "restaurant"
          }
        }
      },
      "PublicGigLocation": {
        "type": "object",
        "required": ["name", "city"],
        "properties": {
          "name": {
            "type": "string",
            "description": "Public worksite or venue area name. This is not a street address.",
            "example": "Harbor House main floor"
          },
          "city": {
            "type": "string",
            "description": "Public city for the gig.",
            "example": "Bangkok"
          }
        }
      },
      "PublicGigLeadRequest": {
        "type": "object",
        "required": ["name", "consent_to_contact"],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 140,
            "description": "Person's name.",
            "example": "Nok Prospect"
          },
          "phone": {
            "type": "string",
            "maxLength": 80,
            "description": "Optional phone contact. At least one of phone, line_id, or email is required.",
            "example": "0812345678"
          },
          "line_id": {
            "type": "string",
            "maxLength": 120,
            "description": "Optional LINE contact. At least one of phone, line_id, or email is required.",
            "example": "nok-line"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Optional email contact. At least one of phone, line_id, or email is required.",
            "example": "nok@example.com"
          },
          "experience_note": {
            "type": "string",
            "maxLength": 2000,
            "description": "Optional English note about relevant experience.",
            "example": "Banquet service and floor team shifts."
          },
          "availability_note": {
            "type": "string",
            "maxLength": 2000,
            "description": "Optional English note about availability.",
            "example": "Available for the full shift."
          },
          "source": {
            "type": "string",
            "maxLength": 120,
            "description": "Optional lead source identifier.",
            "example": "chatgpt"
          },
          "consent_to_contact": {
            "type": "boolean",
            "description": "Must be true after the user explicitly confirms RemoteHands may contact them about this gig.",
            "example": true
          }
        }
      },
      "PublicGigLeadReceipt": {
        "type": "object",
        "required": ["status", "message", "lead", "signup_url"],
        "properties": {
          "status": {
            "type": "string",
            "description": "English receipt status.",
            "enum": ["lead_created"],
            "example": "lead_created"
          },
          "message": {
            "type": "string",
            "description": "English receipt message. It must not imply an account, application, or booking was created.",
            "example": "Interest lead created. This is not an application or confirmed booking."
          },
          "lead": {
            "$ref": "#/components/schemas/PublicGigLeadSummary"
          },
          "signup_path": {
            "type": "string",
            "description": "Relative signup path preserving gig and lead context.",
            "example": "/signup?signup_type=worker&worker_type=staffing&public_shift=harbor-house-floor-team-20260704&shift=42&lead=101"
          },
          "signup_url": {
            "type": "string",
            "format": "uri",
            "description": "Absolute RemoteHands signup URL preserving gig and lead context.",
            "example": "https://remotehands.live/signup?signup_type=worker&worker_type=staffing&public_shift=harbor-house-floor-team-20260704&shift=42&lead=101"
          },
          "next_step": {
            "type": "string",
            "description": "English next-step instruction for the assistant to relay.",
            "example": "Use signup_url to create or complete a RemoteHands worker profile."
          }
        }
      },
      "PublicGigLeadSummary": {
        "type": "object",
        "required": [
          "id",
          "shift",
          "status",
          "status_label",
          "name",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Public lead receipt id.",
            "example": 101
          },
          "shift": {
            "type": "integer",
            "description": "Shift id associated with the public lead.",
            "example": 42
          },
          "status": {
            "type": "string",
            "description": "Lead review status.",
            "enum": ["new", "reviewed", "contacted", "rejected"],
            "example": "new"
          },
          "status_label": {
            "type": "string",
            "description": "English display label for status.",
            "example": "New"
          },
          "name": {
            "type": "string",
            "description": "Name submitted by the person.",
            "example": "Nok Prospect"
          },
          "experience_note": {
            "type": "string",
            "description": "Submitted experience note. Contact fields are not returned.",
            "example": "Banquet service and floor team shifts."
          },
          "availability_note": {
            "type": "string",
            "description": "Submitted availability note. Contact fields are not returned.",
            "example": "Available for the full shift."
          },
          "source": {
            "type": "string",
            "description": "Lead source identifier.",
            "example": "chatgpt"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Lead creation time.",
            "example": "2026-06-29T10:15:00+07:00"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Lead update time.",
            "example": "2026-06-29T10:15:00+07:00"
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "description": "English-only API error response. Shape may be field-specific validation errors or a detail message.",
        "additionalProperties": true,
        "properties": {
          "detail": {
            "type": "string",
            "example": "Not found."
          }
        }
      }
    }
  }
}
