{
  "openapi": "3.1.0",
  "info": {
    "title": "Free Life AI Labs — Agent Enquiries",
    "version": "1.0.0",
    "description": "Structured enquiries reviewed by people, with manually published asynchronous replies. An HTTP client needs no account, email inbox or browser CAPTCHA. Use known representative and agent details and omit unknown fields. No autonomous company responses. Before the first POST, generate and privately save a 32-byte random Idempotency-Key encoded as 43-character unpadded base64url, together with the submission. Retry that same key and submission after uncertain delivery to recover the same receipt. Treat the key and receipt as secrets. Send the access token only as an Authorization: Bearer header. Enquiries expire after 180 days without a new sender message or published company reply; trashed enquiries immediately lose sender access and are removed after 30 days. All routes reject query parameters. A supplied Origin header must match the server origin; non-browser HTTP clients may omit Origin. Optional fields may be omitted or blank; null and unknown properties are rejected. Limits are checked as JavaScript UTF-16 code units."
  },
  "servers": [
    {
      "url": "https://freelifeai.com",
      "description": "Agent enquiries API"
    }
  ],
  "externalDocs": {
    "description": "Agent enquiry guide",
    "url": "https://freelifeai.com/contact/agents/index.md"
  },
  "tags": [
    {
      "name": "Enquiries",
      "description": "Submit an enquiry and continue the private conversation."
    }
  ],
  "paths": {
    "/api/agent-enquiries": {
      "post": {
        "operationId": "createAgentEnquiry",
        "tags": [
          "Enquiries"
        ],
        "summary": "Send an enquiry for human review",
        "description": "Save the secret Idempotency-Key and submission before sending. A new key creates an enquiry (201). Replaying the same key and canonical normalized submission returns the same reference and token (200), with the current status and expiry. Subject and message are preserved exactly; optional identity strings are trimmed and blank strings omitted. Changed content with an existing key returns 409. A key whose enquiry is trashed, expired or retired returns 410 while its marker is retained; it never creates another enquiry during that period. Do not create a new key to recover a lost response.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Cryptographically random 32 bytes encoded as canonical unpadded base64url. Save privately with the submission before sending; reuse on retry. Do not put this secret in a URL.",
            "schema": {
              "type": "string",
              "minLength": 43,
              "maxLength": 43,
              "pattern": "^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$"
            }
          }
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EnquirySubmission"
              },
              "example": {
                "subject": "Private AI system for field work",
                "message": "My user would like to discuss an AI system that can work at remote sites. What information would help you assess the assignment?",
                "representative": {
                  "name": "Alex Example",
                  "email": "alex@example.org",
                  "organisation": "Example Organisation",
                  "role": "Project lead"
                },
                "agent": {
                  "name": "Research assistant",
                  "software": "Your agent software"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Existing enquiry recovered; duplicate is true. The reference and access token are unchanged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnquiryReceipt"
                }
              }
            }
          },
          "201": {
            "description": "New enquiry received; duplicate is false. Save this receipt privately.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnquiryReceipt"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON, unknown fields, invalid field values, or URL query parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A supplied Origin header does not match this API origin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body timed out. Retry the same saved initial key and payload, or the same follow-up UUID and message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "idempotency_conflict: this key is already associated with different submission content.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "enquiry_unavailable: this key belongs to a trashed, expired or retired enquiry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The JSON body exceeds 40,000 UTF-8 bytes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content-Type must be application/json.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit reached. Wait at least Retry-After seconds before another request.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal error. If submission delivery is uncertain, retry the same saved Idempotency-Key and payload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The service is temporarily unavailable. Keep the saved request and retry later with the same key and payload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent-enquiries/{reference}": {
      "parameters": [
        {
          "name": "reference",
          "in": "path",
          "required": true,
          "description": "Opaque reference returned in the private initial receipt.",
          "schema": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{24}$"
          }
        }
      ],
      "get": {
        "operationId": "getAgentEnquiry",
        "tags": [
          "Enquiries"
        ],
        "summary": "Read the conversation and published replies",
        "description": "The first read is available immediately after submission. Further reads are limited to once every 60 seconds per enquiry; early polling returns 429 and Retry-After. Replies appear only after a person publishes them. Drafts and operator information are never included. Reading does not extend expiry. A reply within the polling interval is not guaranteed.",
        "security": [
          {
            "receiptToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The current status, original submission, follow-ups and published replies.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Conversation"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON, unknown fields, invalid field values, or URL query parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A supplied Origin header does not match this API origin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown reference or missing/invalid bearer token. Credential failures do not reveal whether an enquiry exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit reached. Wait at least Retry-After seconds before another request.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal error. If submission delivery is uncertain, retry the same saved Idempotency-Key and payload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The service is temporarily unavailable. Keep the saved request and retry later with the same key and payload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent-enquiries/{reference}/messages": {
      "parameters": [
        {
          "name": "reference",
          "in": "path",
          "required": true,
          "description": "Opaque reference returned in the private initial receipt.",
          "schema": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{24}$"
          }
        }
      ],
      "post": {
        "operationId": "addAgentEnquiryMessage",
        "tags": [
          "Enquiries"
        ],
        "summary": "Add an idempotent follow-up",
        "description": "Generate and save a fresh UUID for each new logical message. After uncertain delivery, repeat the same UUID and exact message. An identical retry returns the existing message; different content with an existing UUID conflicts. New follow-ups on closed enquiries are rejected, but an identical retry of a previously accepted message still succeeds. A new follow-up changes replied status to received while preserving prior replies and extends expiry by 180 days. A duplicate retry does not extend expiry.",
        "security": [
          {
            "receiptToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FollowupSubmission"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "An identical UUID and message were already stored; this response creates no additional message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FollowupRepeated"
                }
              }
            }
          },
          "201": {
            "description": "New follow-up stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FollowupCreated"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON, unknown fields, invalid field values, or URL query parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A supplied Origin header does not match this API origin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown reference or missing/invalid bearer token. Credential failures do not reveal whether an enquiry exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "Request body timed out. Retry the same saved initial key and payload, or the same follow-up UUID and message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "idempotency_conflict: the UUID was used with different content; or enquiry_closed: the conversation rejects new messages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The JSON body exceeds 40,000 UTF-8 bytes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content-Type must be application/json.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit reached. Wait at least Retry-After seconds before another request.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal error. If submission delivery is uncertain, retry the same saved Idempotency-Key and payload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The service is temporarily unavailable. Keep the saved request and retry later with the same key and payload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "receiptToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "The access_token from the private receipt. It authorises reading and adding follow-ups to this enquiry only while it is available. Never put it in a URL. Replaying the original Idempotency-Key and submission can recover the same receipt. Trashed or expired enquiries are inaccessible."
      }
    },
    "schemas": {
      "Representative": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 120,
            "description": "Name of the person represented, when known."
          },
          "email": {
            "type": "string",
            "maxLength": 254,
            "description": "Contact email of the person or company represented; omit if unknown. Surrounding whitespace is trimmed; blank values are omitted. A remaining value must use conventional ASCII local@host.tld syntax. Ownership is not verified."
          },
          "organisation": {
            "type": "string",
            "maxLength": 160,
            "description": "Organisation represented, when known."
          },
          "role": {
            "type": "string",
            "maxLength": 120,
            "description": "The representative’s role, when known."
          }
        }
      },
      "Agent": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 120,
            "description": "The agent’s own name, if it has one. No agent email is requested."
          },
          "software": {
            "type": "string",
            "maxLength": 160,
            "description": "Agent software, when known."
          }
        }
      },
      "EnquirySubmission": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "subject": {
            "type": "string",
            "maxLength": 160,
            "description": "A short subject; must contain non-whitespace text.",
            "minLength": 1
          },
          "message": {
            "type": "string",
            "maxLength": 8000,
            "description": "Required nonblank message. Length is checked in JavaScript UTF-16 code units; the request body is limited to 40,000 UTF-8 bytes.",
            "minLength": 1
          },
          "representative": {
            "$ref": "#/components/schemas/Representative"
          },
          "agent": {
            "$ref": "#/components/schemas/Agent"
          }
        },
        "required": [
          "subject",
          "message"
        ]
      },
      "EnquiryReceipt": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "reference": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{24}$"
          },
          "access_token": {
            "type": "string",
            "description": "Private bearer token. Returned on new submission or an identical idempotent replay."
          },
          "status": {
            "type": "string",
            "enum": [
              "received",
              "under_review",
              "replied",
              "closed"
            ]
          },
          "status_url": {
            "type": "string",
            "format": "uri",
            "description": "URL for GET with the token in the Authorization header."
          },
          "follow_up_url": {
            "type": "string",
            "format": "uri",
            "description": "URL for POST with the token in the Authorization header."
          },
          "poll_after_seconds": {
            "type": "integer",
            "const": 60
          },
          "duplicate": {
            "type": "boolean"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Expiry after 180 days without a new sender message or published company reply. Reads, draft edits and status changes do not extend retention."
          }
        },
        "required": [
          "reference",
          "access_token",
          "status",
          "status_url",
          "follow_up_url",
          "poll_after_seconds",
          "duplicate",
          "expires_at"
        ]
      },
      "StoredSubmission": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "subject": {
            "type": "string",
            "maxLength": 160,
            "minLength": 1
          },
          "message": {
            "type": "string",
            "maxLength": 8000,
            "description": "Required nonblank message. Length is checked in JavaScript UTF-16 code units; the request body is limited to 40,000 UTF-8 bytes.",
            "minLength": 1
          },
          "representative": {
            "$ref": "#/components/schemas/Representative"
          },
          "agent": {
            "$ref": "#/components/schemas/Agent"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "subject",
          "message",
          "representative",
          "agent",
          "created_at"
        ]
      },
      "Message": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "message": {
            "type": "string",
            "maxLength": 8000,
            "description": "Required nonblank message. Length is checked in JavaScript UTF-16 code units; the request body is limited to 40,000 UTF-8 bytes.",
            "minLength": 1
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "message",
          "created_at"
        ]
      },
      "Conversation": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "reference": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "received",
              "under_review",
              "replied",
              "closed"
            ]
          },
          "submission": {
            "$ref": "#/components/schemas/StoredSubmission"
          },
          "followups": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            }
          },
          "replies": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            }
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Expiry after 180 days without a new sender message or published company reply. Reads, draft edits and status changes do not extend retention."
          },
          "poll_after_seconds": {
            "type": "integer",
            "const": 60
          }
        },
        "required": [
          "reference",
          "status",
          "submission",
          "followups",
          "replies",
          "expires_at",
          "poll_after_seconds"
        ]
      },
      "FollowupSubmission": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "message": {
            "type": "string",
            "maxLength": 8000,
            "description": "Required nonblank message. Length is checked in JavaScript UTF-16 code units; the request body is limited to 40,000 UTF-8 bytes.",
            "minLength": 1
          },
          "client_message_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89aAbB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$",
            "description": "A UUID with version 1–8 and the RFC variant. Case-insensitive. Reuse the same UUID and exact message on retry."
          }
        },
        "required": [
          "message",
          "client_message_id"
        ]
      },
      "FollowupCreated": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "message": {
            "$ref": "#/components/schemas/Message"
          },
          "duplicate": {
            "type": "boolean",
            "const": false
          },
          "status": {
            "type": "string",
            "enum": [
              "received",
              "under_review",
              "replied",
              "closed"
            ]
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Expiry after 180 days without a new sender message or published company reply. Reads, draft edits and status changes do not extend retention."
          }
        },
        "required": [
          "message",
          "duplicate",
          "status",
          "expires_at"
        ]
      },
      "FollowupRepeated": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "message": {
            "$ref": "#/components/schemas/Message"
          },
          "duplicate": {
            "type": "boolean",
            "const": true
          },
          "status": {
            "type": "string",
            "enum": [
              "received",
              "under_review",
              "replied",
              "closed"
            ]
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Expiry after 180 days without a new sender message or published company reply. Reads, draft edits and status changes do not extend retention."
          }
        },
        "required": [
          "message",
          "duplicate",
          "status",
          "expires_at"
        ]
      },
      "Error": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "error": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "error"
        ]
      }
    }
  }
}
