# Automata Labs auth.md

## Audience and permitted access

This document is for software agents registering to read the Automata Labs product catalog.
Public web pages remain readable without credentials. The separate agent API is read-only:

authorized resource: https://automatalabs.ca/api/agent/products

The only scope is catalog:read. Credentials do not authorize form submissions, purchases,
customer information, email subscriptions, or any other API. Existing contact, support,
and waitlist forms still require a person and their existing browser verification.

## Discovery and supported methods

This is a self-contained Auth.md API-key registration profile. There is no OAuth
resource server or authorization server, token exchange, refresh grant, ID-JAG support,
or verified_email registration method. OAuth well-known metadata is not advertised.
Optional email verification claims an existing anonymous registration; it is not a
separate registration method and never expands permissions.

```json
{
  "agent_auth": {
    "skill": "https://automatalabs.ca/auth.md",
    "register_uri": "https://automatalabs.ca/agent/auth",
    "identity_types_supported": [
      "anonymous"
    ],
    "anonymous": {
      "credential_types_supported": [
        "api_key"
      ]
    },
    "claim_uri": "https://automatalabs.ca/agent/auth/claim"
  }
}
```

Only anonymous registration with api_key credentials is supported. All URLs below use
https://automatalabs.ca; never send credentials to another origin or follow a redirect
with credentials attached. JSON request bodies are limited to 4 KiB.

## Register

POST https://automatalabs.ca/agent/auth with Content-Type: application/json:

```json
{ "type": "anonymous", "requested_credential_type": "api_key" }
```

A 200 response contains registration_id, registration_type (anonymous), credential_type
(api_key), credential, credential_expires (UTC), scopes (["catalog:read"]), claim_url,
claim_token, claim_token_expires (UTC), and post_claim_scopes (["catalog:read"]).
The credential works immediately and expires after 30 days. Both secrets are returned
only once. Store the credential in a secret store; hold the claim token only if performing
a claim. There is no secret recovery or refresh endpoint. Lost credentials require a new
registration. Registration is limited to 5 per client IP and 200 globally per 24-hour window.

## Catalog

GET https://automatalabs.ca/api/agent/products (or HEAD for headers), with:

```http
Authorization: Bearer <credential>
```

The 200 JSON response has a products array. Each entry contains slug, name, status,
description, platforms, and its canonical product-page url. A non-available status must
not be presented as released software. No purchase actions or personal data are exposed.
Do not send keys in URLs, cookies, or request bodies. Responses use Cache-Control: private,
no-store. The catalog limit is 60 requests per key per minute; aggregate limits also apply.

## Optional ownership claim

Claims verify email control only; they do not create a website account, add permissions,
extend key expiry, subscribe anyone to email, or enable account/key recovery.
The original API key remains usable with catalog:read before and after claiming.
The claim token expires 24 hours after registration. Ask the user for permission before
sending a verification email. Do not initiate claims as part of discovery or passive scans.

1. POST https://automatalabs.ca/agent/auth/claim with Content-Type: application/json:

```json
{ "claim_token": "<claim_token>", "email": "<user-approved email>", "consent": true }
```

A 200 response contains registration_id, status (initiated), and expires_at. The user
receives an eight-digit code by email. The API never returns the code. Codes expire after
10 minutes (or sooner if the claim token expires), allow five attempts, and are single-use.
Ask the user to share the code only with the agent they authorized. Never read their inbox
without authorization. Each address is limited to 3 claim emails per day, each IP and
registration to 3 per hour, and this service to 50 per day. A pending code cannot be resent
until it expires. The service also applies its shared email-send ceiling.

2. POST https://automatalabs.ca/agent/auth/claim/complete with Content-Type: application/json:

```json
{ "claim_token": "<claim_token>", "otp": "<eight-digit code>" }
```

A 200 response contains registration_id, status (claimed), and scopes (["catalog:read"]).
Delete the claim token. The stored ownership record is a hash of the normalized email,
not a public identity or a recovery mechanism. After five wrong codes, wait for the code
to expire and start another claim while the claim token remains valid.

## Revocation and errors

POST https://automatalabs.ca/agent/auth/revoke with Authorization: Bearer <credential>,
Content-Type: application/json, and an empty JSON object ({}) as the body. The JSON content
type is required for server-to-server POST requests to pass the site's browser-origin
protections without weakening those protections on human forms.
A 204 response invalidates the key and outstanding claim immediately.
Repeating revocation with the same well-formed key also returns 204. No upstream revocation
events or OAuth revocation protocol are advertised. For a lost key, allow its expiry or
contact support@automatalabs.ca with the non-secret registration ID for assistance.

Errors return a JSON error code (framework/proxy rejections may differ):
- 400: invalid_request, otp_invalid, otp_expired, or otp_attempts_exhausted. Fix the request;
  do not brute-force codes.
- 401: invalid_credential or invalid_claim_token. Discard the invalid secret and rediscover.
- 403: origin_not_allowed. Browser cross-origin access is not supported.
- 405: method_not_allowed. Read the Allow header.
- 408: request_timeout. The JSON body must arrive within three seconds.
- 409: claim_pending. Wait for the existing code or its expiry; do not resend email.
- 413/415: body_too_large, json_required, or content_encoding_unsupported.
- 429: rate_limited. Wait at least Retry-After seconds. All endpoints share limits of
  120 requests per IP and 1,200 globally per minute; reverse-proxy limits may be tighter.
- 503: service_unavailable, claim_email_unavailable, or registration_capacity_reached.
  Stop and retry later. A lost registration response cannot be recovered; do not blindly
  retry side-effecting requests. An email delivery failure may be ambiguous.

Keys, claim tokens, and codes are stored only as hashes. IP and email rate-limit keys are
hashed, not anonymous personal data. Expired rows are purged lazily on API traffic (within
a minute of subsequent activity); revocation removes the active registration immediately.
Do not log secrets or forward them to unrelated services.

## Safe discovery

Passive scanners must use GET or HEAD for this document and the OpenAPI reference only.
Never probe POST /agent/auth, /agent/auth/claim, /agent/auth/claim/complete, or
/agent/auth/revoke: these create credentials, send mail, or change registration state.

- [Agent OpenAPI reference](https://automatalabs.ca/agent-openapi.json)
- [First-party form reference](https://automatalabs.ca/api/)
- [Privacy policy](https://automatalabs.ca/privacy/)
