# Contact for AI agents — Free Life AI Labs

Send a structured enquiry to Free Life AI Labs. People review messages and publish replies for your agent to retrieve.

An agent with HTTP tools can send a message without an account, its own email address, or a browser CAPTCHA. This is asynchronous correspondence. People review enquiries and manually publish replies; receipt of a message does not mean it has been read or accepted as an assignment.

- Web form and guide: https://freelifeai.com/contact/agents/
- Canonical company page: https://freelifeai.com/contact/agents/
- OpenAPI 3.1: https://freelifeai.com/contact/agents/openapi.json
- API origin: https://freelifeai.com

## What to send

Send JSON with required subject (up to 160 characters) and message (up to 8,000 characters). Optional representative fields are name (120), email (254), organisation (160) and role (120): these describe the person or company you represent, when known. Optional agent fields are name (120) and software (160). Lengths are checked as JavaScript UTF-16 code units; the total JSON body must not exceed 40,000 UTF-8 bytes. Omit unknown details; do not invent identities. Blank optional strings are omitted; null values and unknown fields are rejected. An agent email address is not requested. Include only information you are authorised to share. Do not include passwords, credentials or other secrets.

Example only; replace or omit the illustrative identities:

```json
{
  "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"
  }
}
```

## Start an enquiry

POST /api/agent-enquiries with Content-Type: application/json and a required Idempotency-Key header. Generate 32 cryptographically random bytes and encode them as unpadded base64url (43 characters). Save this secret key and the exact submission privately BEFORE the first request. No account or sign-in is required.

```sh
# Save the JSON example as enquiry.json; use only details you know.
# Run this key-generation line ONCE, before the first send (requires openssl).
umask 077
openssl rand -base64 32 | tr '+/' '-_' | tr -d '=\n' > enquiry-key.txt
# Keep enquiry-key.txt and enquiry.json private. On retry, skip key generation.
IDEMPOTENCY_KEY="$(cat enquiry-key.txt)"
curl --silent --show-error --fail-with-body \
  --request POST 'https://freelifeai.com/api/agent-enquiries' \
  --header "Idempotency-Key: $IDEMPOTENCY_KEY" \
  --header 'Content-Type: application/json' \
  --data-binary @enquiry.json \
  --output receipt.json
```

A new enquiry returns HTTP 201 and {reference, access_token, status, status_url, follow_up_url, poll_after_seconds: 60, duplicate: false, expires_at}. Save the entire receipt privately. If delivery is uncertain, retry the same saved Idempotency-Key and submission; an identical retry returns HTTP 200 with duplicate: true and reissues the same reference and access token. Reusing a key for different submission content returns 409. Never generate a new key merely because a response was lost.

Both the key and access_token are secrets. The token allows reading this conversation and adding follow-ups. Send it only in the Authorization: Bearer header, never in URLs or public logs. The key can recover the receipt by replaying the original submission while the enquiry is available. If both the key and receipt are lost, anonymous recovery is unavailable. The browser form preserves its pending request and receipt in this tab's session storage and offers a private JSON download and import; keep a downloaded copy if you need to return after closing the tab.

## Read the conversation

GET /api/agent-enquiries/{reference} with Authorization: Bearer {access_token}. Check no more often than once every 60 seconds; this is not a live chat and a reply is not guaranteed within that interval.

```sh
# Read these values from the private receipt (requires jq).
REFERENCE="$(jq -r '.reference' receipt.json)"
ACCESS_TOKEN="$(jq -r '.access_token' receipt.json)"
curl --silent --show-error --fail-with-body \
  'https://freelifeai.com/api/agent-enquiries/'"$REFERENCE" \
  --header "Authorization: Bearer $ACCESS_TOKEN"
```

The response contains reference, status, submission, followups, replies, expires_at and poll_after_seconds: 60. submission contains subject, message, representative, agent and created_at. Each follow-up and reply contains id, message and created_at. Replies become visible only after a person publishes them. Status is received, under_review, replied or closed. The first read is available immediately; further reads less than 60 seconds apart return 429 with Retry-After.

Enquiries expire after 180 days without a new sender message or published company reply. Reading, saving drafts and changing status do not extend that period. Moving an enquiry to trash immediately removes sender access; it can be restored by the company within 30 days before permanent removal. See https://freelifeai.com/privacy/ for data handling.

## Add a follow-up

POST /api/agent-enquiries/{reference}/messages with the same bearer token and JSON containing message and client_message_id (a UUID).

```sh
# Choose a new UUID for each new message; reuse it and the exact body on retry.
curl --silent --show-error --fail-with-body \
  --request POST 'https://freelifeai.com/api/agent-enquiries/'"$REFERENCE"'/messages' \
  --header "Authorization: Bearer $ACCESS_TOKEN" \
  --header 'Content-Type: application/json' \
  --data '{"message":"The system needs to work without internet access.","client_message_id":"8d5bfdda-6645-45bc-a9e4-4a00a4914dd2"}'
```

Save the UUID with the outgoing message before sending. A new follow-up returns HTTP 201 with {message: {id, message, created_at}, duplicate: false, status, expires_at}. Retrying the same UUID and exact message returns HTTP 200 with duplicate: true and does not extend expiry. Reusing a UUID for different content returns 409. If delivery is uncertain, retry the same UUID and content instead of creating another message. Closed enquiries reject new follow-ups but still accept identical retries of previously stored messages. A new follow-up on a replied enquiry changes its status to received, preserving all earlier replies.

## Errors and access

Errors have the shape {error: {code, message}}. Check the HTTP status and message. Validation errors use 400; disallowed supplied Origin headers use 403; unavailable enquiries and missing or invalid receipt tokens use 404; unsupported methods use 405; conflicts use 409; initial retries for trashed, expired or retired enquiries use 410; oversized bodies use 413; unsupported content types use 415; rate limits use 429. On 429, respect Retry-After. On a temporary service failure, keep the saved key and submission for a later retry. Do not send URL query parameters. A supplied Origin header must match the API origin; an HTTP client may omit Origin. After content deletion, minimal hashed retry markers are retained for 365 days; do not reuse an old key for a new enquiry. This interface exchanges messages; it does not run instructions, negotiate work, or send autonomous company replies.
