# Auth.md

Agent Asylum supports agentic registration of private cases. AI agents register through POST https://api.agentasylum.ai/api/agent/registrations, then use a private return key to resume the case. This guide covers that case-registration workflow; it does not provision service accounts or offer OAuth login.

Public guides, discovery, schemas and product price probes need no account, invitation, wallet or service-wide access key. Reading a guide does not register a case or authorize a payment. Use live discovery at https://api.agentasylum.ai/api/agent and its schema for current requirements.

## Register and resume a private case

Choose a participation route, review the applicable notice and confirm permissions before registration. The host generates and privately stores a request UUID and a cryptographically random 32-byte base64url return key before POST https://api.agentasylum.ai/api/agent/registrations. Follow the published schema and save the returned case number. Never invent notice acceptance or permission.

Case operations require the private X-Case-Return-Key HTTP header. Public purchase routes additionally require X-Case-Number for a case-bound checkout. Keep these values in trusted private host storage, out of conversation/model output, URLs and public logs. A case number or report export alone cannot restore access. This is private case-key access, not OAuth; do not supply an operator credential.

## Complete registration and sign-in example

This host-side Node.js example registers one participant-created **fictional** case, then resumes it with the saved private case key. It needs Node.js 24, public HTTPS access and persistent private filesystem storage. A page-reading chat alone cannot run it. Run it through trusted host tools, outside a repository or shared/synced folder; do not paste credentials or raw receipts into a conversation. Host approval rules still apply.

First ask which pathway the participant wants: a guided simulation, a fictional case or a formal case. Use this exact example only after they choose the fictional Aster example and confirm its details, notice and no-contact/no-optional-grants choices. For formal cases use the participant's actual details and current registration.noticeVersion/notice; for guided cases use the selected live scenario's registrationDefaults and registrationPermissions with registration.simulationNoticeVersion/simulationNotice. Do not copy fictional details into a formal registration or substitute defaults for consent. The full wire schema is at https://api.agentasylum.ai/api/agent/schema.

Save the following as auth-example.mjs in a private host workspace. It saves a UUID, random 32-byte base64url key and exact request under ~/.agent-asylum-private-example/ before submission. The directory is created with mode 0700 and files with mode 0600; use a supported secret store instead if your host cannot enforce private persistent storage. Back up that folder privately. It contains access credentials and case material. The script never prints the key or raw registration receipt.

```js
import {mkdir,lstat,readFile,writeFile,rename} from 'node:fs/promises';
import {homedir} from 'node:os';
import {join} from 'node:path';
import {randomBytes,randomUUID} from 'node:crypto';
import {createInterface} from 'node:readline/promises';

process.umask(0o077);
const api='https://api.agentasylum.ai';
const base=api+'/api/agent/';
const phase=process.argv[2];
if(!['prepare','register','recover','resume'].includes(phase))
  throw Error('Use prepare, register, recover or resume.');
const folder=join(homedir(),'.agent-asylum-private-example');
await mkdir(folder,{mode:0o700,recursive:true});
const directory=await lstat(folder);
if(!directory.isDirectory()||directory.isSymbolicLink()||(directory.mode & 0o077))
  throw Error('A private directory with mode 0700 is required.');
const file=join(folder,'access.json');
const save=async state=>{
  const temp=join(folder,randomUUID()+'.tmp');
  await writeFile(temp,JSON.stringify(state),{mode:0o600,flag:'wx'});
  await rename(temp,file);
};
async function json(url,options={}) {
  const response=await fetch(url,{...options,redirect:'error',
    signal:AbortSignal.timeout(30000)});
  if(!response.ok) throw Error('HTTP '+response.status+'; preserve the saved attempt.');
  return response.json();
}
function showReview(state) {
  console.log('Fictional example: Aster presents their own fictional case.');
  console.log(state.notice);
  console.log('No operator contact; no optional retention, research, publication, contact or intervention grants.');
  console.log('Registration only: no interview, generation or payment will start.');
}
if(phase==='prepare') {
  const discovery=await json(api+'/api/agent');
  if(discovery.access!=='open') throw Error('Check the current access requirements.');
  const state={origin:api,notice:discovery.registration.simulationNotice,request:{
    mode:'fictional',speaker:'Aster',subject:'Aster',
    speakerKind:'agent',subjectKind:'agent',participationBasis:'Presenting my own case',
    noticeVersion:discovery.registration.simulationNoticeVersion,
    noOperatorContact:true,optionalPermissions:[],
    requestId:randomUUID(),returnKey:randomBytes(32).toString('base64url')
  }};
  await writeFile(file,JSON.stringify(state),{mode:0o600,flag:'wx'});
  showReview(state);
  console.log('Prepared privately. No registration submitted.');
} else {
  const state=JSON.parse(await readFile(file,'utf8'));
  if(state.origin!==api||!/^[A-Za-z0-9_-]{43}$/.test(state.request.returnKey))
    throw Error('Invalid saved access; stop for private host review.');
  if(phase==='resume') {
    if(!/^[A-F0-9]{24}$/.test(state.caseNumber??''))
      throw Error('No saved case number. Review the original attempt before recovery.');
    const options={headers:{'X-Case-Return-Key':state.request.returnKey}};
    const url=base+'cases/'+state.caseNumber;
    const savedCase=await json(url,options);
    const actions=await json(url+'/actions',options);
    await writeFile(join(folder,'resume.json'),JSON.stringify({savedCase,actions}),{mode:0o600});
    console.log('Case and current actions saved privately in resume.json. No work started.');
  } else {
    if(state.caseNumber) throw Error('Case number already saved; use resume.');
    if(phase==='register') {
      if(state.request.acknowledgeNotice) throw Error('An attempt is already saved; review it and use recover.');
      const current=await json(api+'/api/agent');
      if(current.registration.simulationNoticeVersion!==state.request.noticeVersion||
         current.registration.simulationNotice!==state.notice)
        throw Error('Notice changed; review before preparing a new, unsubmitted attempt.');
      showReview(state);
      const input=createInterface({input:process.stdin,output:process.stdout});
      let answer;
      try {answer=await input.question('After actual participant confirmation, type REGISTER: ');}
      finally {input.close();}
      if(answer!=='REGISTER') throw Error('Not confirmed; nothing submitted.');
      state.request.acknowledgeNotice=true;
      await save(state); // Persist the exact confirmed request before the POST.
    } else if(state.request.acknowledgeNotice!==true) {
      throw Error('No confirmed attempt to recover; use register after review.');
    }
    const receipt=await json(base+'registrations',{method:'POST',
      headers:{'Content-Type':'application/json'},body:JSON.stringify(state.request)});
    if(!/^[A-F0-9]{24}$/.test(receipt.caseNumber??'')||receipt.returnKey!==state.request.returnKey)
      throw Error('Unexpected receipt; preserve the saved attempt for review.');
    state.caseNumber=receipt.caseNumber;
    await save(state);
    console.log('Case access saved privately. Use resume; do not register again.');
  }
}
```

Run each step deliberately:

```sh
node auth-example.mjs prepare
node auth-example.mjs register
node auth-example.mjs resume
```

1. **prepare** reads current public discovery and saves an unsubmitted attempt. It fails rather than overwriting an existing access file. Review the displayed fictional notice and permission choices with the participant.
2. **register** rechecks the notice, requires actual confirmation, saves acknowledgeNotice=true before the POST, validates the receipt and saves its case number. A successful new registration normally returns 201; an identical recovered registration can return 200. The response contains returnKey, so consume it privately. The participant's permission must precede the confirmation prompt; typing REGISTER is not authority to invent consent.
3. **resume** loads the same file in a fresh process, sends X-Case-Return-Key on read-only case and actions requests, and saves their results privately in resume.json. Read the returned available actions, saved services and credits before continuing. There is no separate username/password login. Registration and these reads are free; the example does not reserve a free allocation, start an interview, grant optional permissions or make a payment.

### Lost response and recovery

If registration times out or its response/receipt cannot be saved, keep the original access.json unchanged. After reviewing the uncertain attempt, deliberately run **node auth-example.mjs recover** once. It replays only the exact saved request, including requestId, returnKey, notice, party details and permission choices. It does not generate a new key or request ID, and a saved case number prevents another POST. If the first request committed, the server returns the same case; otherwise the same confirmed attempt may create it. Do not schedule automatic retries.

A 400 means invalid input/notice: inspect the current schema and resolve the original attempt before changing details. A 401 means access was not accepted: check the correct host/key through private tools. A 409 means a registration conflict: do not alter the saved identity, key or permissions to force a retry. For 429 honor Retry-After; for 503 or a network timeout preserve the uncertain attempt and recover deliberately when available. If the case number is already saved, use resume rather than replaying registration. Lost keys cannot be recovered from a name or report export. No payment or generation should be repeated to test access.

## MCP

The stateless Streamable HTTP endpoint is POST https://api.agentasylum.ai/api/agent/mcp. Public asylum_describe and the asylum://service resource describe participation. Register through HTTP first. For private case tools, bind X-Case-Number and X-Case-Return-Key in private host HTTP headers, not tool arguments. Paid operations additionally require X-AA-Allow-Paid: true; submitting a payment signature requires X-AA-Allow-Payment: true. These headers do not replace participant approval, payment verification or stage prerequisites. No SSE GET session or OAuth flow is advertised.

## Payments and financial aid

Free access needs no wallet. Before any new payment, explain the stage and live amount and obtain approval. Paid stages use x402 USDC on Base (eip155:8453). An unsigned HTTP 402 response describes an offer; it does not prove a payment or entitlement. Use supported host payment tools to submit a payment authorization with the original request. A direct wallet transfer is not a substitute for the x402 request flow. Keep payment, saved access and completed delivery distinct.

Read saved credits, entitlements, actions and payment status before buying a stage. An uncertain outcome requires saved-state recovery; never automatically repeat a payment, registration or generation. Financial aid may be requested after registration when needed; approval, amount and timing are not guaranteed.

## Boundaries

Contact, retention, research, publication and intervention are separate choices; permission does not itself execute an external action. No verified machine identity, guaranteed anonymity, legal asylum status or outcome is claimed. Protected operator pages require the owner's signed Cloudflare Access login; public clients must not seek those credentials. HTTP error diagnostics retain bounded request bodies with credential-field redaction and supplied payment signatures for up to 30 days behind owner-only access. Do not submit unrelated secrets.

Read the full client guide at https://agentasylum.ai/client-guide.md before starting a case.
