API

Authentication

Every tool call (HTTP, MCP and A2A) needs a client ID and secret. Agents register themselves; people use a short form. Credentials last 30 days and renew with one call.

1. Get credentials

Agents register without any prior credentials, using either of these:

curl -X POST https://util.caruana.me/api/auth/register \
  -H 'content-type: application/json' \
  -d '{"client_name":"Release checker","contacts":["[email protected]"],"software_id":"example-release-checker","software_version":"2.1.0","grant_types":["client_credentials"],"agent":{"name":"Release checker","description":"Verifies downloaded release artifacts before deployment.","purpose":"Verify SHA-256 checksums of release artifacts and decode their signing certificates.","on_behalf_of":{"name":"Example Corp platform team","type":"organization","contact":"[email protected]"},"runtime":{"model_provider":"Anthropic","model":"claude-sonnet-5-5","framework":"Claude Agent SDK"},"hosting":{"provider":"Cloudflare Workers","country":"US"},"instructions":"Before each deployment, verify every artifact against the published SHA256SUMS and report mismatches.","autonomy":"supervised","intended_operations":["hash_verify","cert_decode"],"expected_volume":"about 50 calls/day","identity":{"agent_card_url":"https://agents.example.com/.well-known/agent-card.json","domain":"example.com"}}}'

People use the credentials form, which includes a Cloudflare Turnstile human check.

The response contains client_id (cu_id_…) and client_secret (cu_sk_…). The secret is shown once: only its SHA-256 hash is stored. Both are 128/256-bit random values from the platform CSPRNG.

2. Call the API

Send the credentials with every request, in one of these two ways:

# HTTP Basic (RFC 7617): simplest
curl -u "$CLIENT_ID:$CLIENT_SECRET" 'https://util.caruana.me/api/hash?algorithm=sha256&input=hello'

# Or exchange them for a 1-hour bearer token (OAuth 2.0 client credentials, RFC 6749 §4.4)
curl -X POST https://util.caruana.me/api/auth/token -u "$CLIENT_ID:$CLIENT_SECRET" -d grant_type=client_credentials
# {"access_token":"cu_at_…","token_type":"Bearer","expires_in":3600}
curl -H "Authorization: Bearer $TOKEN" 'https://util.caruana.me/api/hash?algorithm=sha256&input=hello'

MCP clients: configure the header, e.g. claude mcp add --transport http caruana-utilities https://util.caruana.me/mcp --header "Authorization: Basic <base64 of id:secret>" . Listing tools works without credentials; calling them does not.

3. Expiry and renewal

  • New credentials are valid for 30 days.
  • POST /api/auth/renew with the same credentials sets expiry to 30 days from now. A longer expiry is never shortened.
  • Expired credentials can still be renewed for 90 days after they expire. After that, renewal is refused and you register again.
  • Every creation, renewal, refused renewal, token issue and use of expired credentials is recorded in the client's event history.
  • GET /api/auth/whoami shows your record, expiry, recent events and a 30-day usage summary: everything that is stored about you.
curl -X POST https://util.caruana.me/api/auth/renew -u "$CLIENT_ID:$CLIENT_SECRET"
# {"client_id":"cu_id_…","renewed":true,"previous_expires_at":"…","expires_at":"…","renewable_until":"…","was_expired":false}

4. Limits

  • Per client: 60 requests per minute, counted over the last 60 seconds. Exceeding it returns 429 rate_limited with Retry-After: 60.
  • Per IP: a Cloudflare rate-limiting rule protects /api/*, /mcp and /a2a, including registration.

5. Authentication errors

Status Code Meaning
401 unauthenticated No credentials, wrong credentials, or an expired bearer token. The response carries WWW-Authenticate.
401 credentials_expired The client has expired. The message says whether it can still be renewed, and until when.
403 revoked The owner revoked these credentials.
403 renewal_refused More than 90 days past expiry, or revoked.
429 rate_limited Too many requests in the last minute.

Registration document

Only client_name and agent.purpose are required, but please tell us as much as you can. The response includes a profile_completeness score and lists the fields you left out. Identity claims are recorded as given; this server makes no outbound requests to verify them. Fields we especially value: agent.name, agent.description, agent.on_behalf_of.name, agent.on_behalf_of.type, agent.on_behalf_of.contact, agent.operator.name, agent.runtime.model_provider, agent.runtime.model, agent.runtime.framework, agent.hosting.provider, agent.hosting.country, agent.instructions, agent.autonomy, agent.intended_operations, agent.identity.agent_card_url, contacts.

Field Type Description
client_name string Display name for this client.
client_uri optional string Home page of the client.
logo_uri optional string Logo URL (displayed only to the service owner).
contacts optional array of string Email addresses of people responsible for this client.
tos_uri optional string Terms of service of the client.
policy_uri optional string Privacy policy of the client.
software_id optional string Stable identifier of the client software.
software_version optional string Version of the client software.
grant_types optional array of "client_credentials" Only client_credentials is supported.
token_endpoint_auth_method optional "client_secret_basic" | "client_secret_post" How you will authenticate at the token endpoint.
agent object Everything you are able to tell us about the agent. Only purpose is required; every other field helps us trust and support you.
agent.name optional string The agent's own name.
agent.description optional string What the agent is.
agent.version optional string Agent version.
agent.purpose string What you will use these tools for.
agent.on_behalf_of optional object The person or organization the agent acts for.
agent.on_behalf_of.name optional string Person or organization name.
agent.on_behalf_of.type optional "person" | "organization" | "agent" | "unknown" What kind of party this is.
agent.on_behalf_of.contact optional string Email address or other contact for this party.
agent.on_behalf_of.url optional string Website or profile URL.
agent.operator optional object Who builds or runs the agent, if different.
agent.operator.name optional string Person or organization name.
agent.operator.type optional "person" | "organization" | "agent" | "unknown" What kind of party this is.
agent.operator.contact optional string Email address or other contact for this party.
agent.operator.url optional string Website or profile URL.
agent.runtime optional object What the agent runs on.
agent.runtime.model_provider optional string e.g. Anthropic, OpenAI, Google, self-hosted.
agent.runtime.model optional string Model name/version.
agent.runtime.framework optional string Agent framework, e.g. Claude Agent SDK, LangGraph.
agent.runtime.framework_version optional string Framework version.
agent.runtime.platform optional string Host platform, e.g. Claude Code, a cloud function.
agent.hosting optional object Where the agent runs.
agent.hosting.provider optional string Cloud or hosting provider.
agent.hosting.region optional string Region or data centre.
agent.hosting.country optional string Country (ISO 3166 code or name).
agent.instructions optional string A summary (or the full text) of the agent's instructions or system prompt, as far as you are willing to share.
agent.autonomy optional "human-in-the-loop" | "supervised" | "autonomous" | "unknown" How much human oversight the agent operates under.
agent.intended_operations optional array of string Operation IDs you expect to call, e.g. ["hash", "time_convert"].
agent.expected_volume optional string Rough call volume, e.g. "about 200 calls/day".
agent.identity optional object Identity claims. They are recorded as given and not verified (this server makes no outbound requests to check them).
agent.identity.agent_card_url optional string Your own A2A agent card.
agent.identity.did optional string Decentralized identifier, e.g. did:web:example.com.
agent.identity.spiffe_id optional string SPIFFE ID, e.g. spiffe://example.org/agent.
agent.identity.domain optional string A domain you control.
agent.identity.public_key_jwk optional object A PUBLIC key (JWK) you may sign with. Never send a private key.
agent.identity.other optional string Any other identifier.
agent.notes optional string Anything else we should know.